diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index 9f2bd024..0b5ec4ff 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -33,7 +33,8 @@
"WebSearch",
"Bash(mix shellcheck:*)",
"Bash(mix compile:*)",
- "mcp__tidewave__get_docs"
+ "mcp__tidewave__get_docs",
+ "mcp__tidewave__search_package_docs"
]
},
"enableAllProjectMcpServers": false
diff --git a/lib/music_library/records.ex b/lib/music_library/records.ex
index 22942463..93d8910c 100644
--- a/lib/music_library/records.ex
+++ b/lib/music_library/records.ex
@@ -159,6 +159,15 @@ defmodule MusicLibrary.Records do
end)
end
+ def list_genres do
+ q =
+ from r in fragment("records, json_each(records.genres)"),
+ select: fragment("DISTINCT value"),
+ order_by: fragment("value COLLATE NOCASE ASC")
+
+ Repo.all(q)
+ end
+
def get_record!(id), do: Repo.get!(Record, id)
def get_release_status(release_id, format) do
diff --git a/lib/music_library_web/components/record_form.ex b/lib/music_library_web/components/record_form.ex
index f28a09fd..45130bb8 100644
--- a/lib/music_library_web/components/record_form.ex
+++ b/lib/music_library_web/components/record_form.ex
@@ -60,6 +60,179 @@ defmodule MusicLibraryWeb.Components.RecordForm do
label={gettext("Purchased at")}
/>
+
<.label for="dominant_colors">
@@ -164,7 +337,10 @@ defmodule MusicLibraryWeb.Components.RecordForm do
|> assign(assigns)
|> assign_new(:form, fn ->
to_form(Records.change_record(record))
- end)}
+ end)
+ |> assign_new(:all_genres, fn -> Records.list_genres() end)
+ |> assign_new(:genre_query, fn -> "" end)
+ |> assign_new(:genre_suggestions, fn -> [] end)}
end
@impl true
@@ -192,6 +368,57 @@ defmodule MusicLibraryWeb.Components.RecordForm do
{:noreply, assign(socket, form: to_form(changeset, action: :validate))}
end
+ def handle_event("search_genres", %{"value" => query}, socket) do
+ current_genres = get_current_genres(socket.assigns)
+ query = String.trim(query)
+
+ suggestions =
+ if query == "" do
+ []
+ else
+ downcased_query = String.downcase(query)
+
+ socket.assigns.all_genres
+ |> Enum.filter(fn g ->
+ String.contains?(String.downcase(g), downcased_query) and g not in current_genres
+ end)
+ |> Enum.take(10)
+ end
+
+ {:noreply,
+ socket
+ |> assign(:genre_query, query)
+ |> assign(:genre_suggestions, suggestions)}
+ end
+
+ def handle_event("add_genre", %{"genre" => genre}, socket) do
+ genre = genre |> String.trim() |> String.downcase()
+ current_genres = get_current_genres(socket.assigns)
+
+ if genre == "" or genre in current_genres do
+ {:noreply, socket |> assign(:genre_query, "") |> assign(:genre_suggestions, [])}
+ else
+ new_genres = current_genres ++ [genre]
+ params = Map.merge(socket.assigns.form.params, %{"genres" => new_genres})
+ changeset = Records.change_record(socket.assigns.record, params)
+
+ {:noreply,
+ socket
+ |> assign(:form, to_form(changeset, action: :validate))
+ |> assign(:genre_query, "")
+ |> assign(:genre_suggestions, [])}
+ end
+ end
+
+ def handle_event("remove_genre", %{"genre" => genre}, socket) do
+ current_genres = get_current_genres(socket.assigns)
+ new_genres = Enum.reject(current_genres, &(&1 == genre))
+ params = Map.merge(socket.assigns.form.params, %{"genres" => new_genres})
+ changeset = Records.change_record(socket.assigns.record, params)
+
+ {:noreply, assign(socket, :form, to_form(changeset, action: :validate))}
+ end
+
defp save_record(socket, record_params, uploaded_covers) do
params =
case uploaded_covers do
@@ -236,5 +463,9 @@ defmodule MusicLibraryWeb.Components.RecordForm do
end)
end
+ defp get_current_genres(assigns) do
+ Ecto.Changeset.get_field(assigns.form.source, :genres) || []
+ end
+
defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
end
diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot
index 67daf110..7484bc03 100644
--- a/priv/gettext/default.pot
+++ b/priv/gettext/default.pot
@@ -83,6 +83,7 @@ msgstr ""
msgid "Formats"
msgstr ""
+#: lib/music_library_web/components/record_form.ex
#: lib/music_library_web/live/collection_live/show.html.heex
#: lib/music_library_web/live/wishlist_live/show.html.heex
#, elixir-autogen, elixir-format
@@ -1821,3 +1822,13 @@ msgstr ""
#, elixir-autogen, elixir-format
msgid "No records in this set yet"
msgstr ""
+
+#: lib/music_library_web/components/record_form.ex
+#, elixir-autogen, elixir-format
+msgid "Create \"%{genre}\""
+msgstr ""
+
+#: lib/music_library_web/components/record_form.ex
+#, elixir-autogen, elixir-format
+msgid "Search or add genres..."
+msgstr ""
diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po
index f9d627d8..4ca2a783 100644
--- a/priv/gettext/en/LC_MESSAGES/default.po
+++ b/priv/gettext/en/LC_MESSAGES/default.po
@@ -83,6 +83,7 @@ msgstr ""
msgid "Formats"
msgstr ""
+#: lib/music_library_web/components/record_form.ex
#: lib/music_library_web/live/collection_live/show.html.heex
#: lib/music_library_web/live/wishlist_live/show.html.heex
#, elixir-autogen, elixir-format
@@ -1821,3 +1822,13 @@ msgstr ""
#, elixir-autogen, elixir-format, fuzzy
msgid "No records in this set yet"
msgstr ""
+
+#: lib/music_library_web/components/record_form.ex
+#, elixir-autogen, elixir-format
+msgid "Create \"%{genre}\""
+msgstr ""
+
+#: lib/music_library_web/components/record_form.ex
+#, elixir-autogen, elixir-format
+msgid "Search or add genres..."
+msgstr ""