defmodule FreediveWeb.LiliformLive do @callback items() :: map @callback filters() :: [map] @callback search(items :: map, query :: String.t()) :: map defmacro __using__(opts) do quote location: :keep, bind_quoted: [opts: opts] do use FreediveWeb, :live_view require Logger @behaviour FreediveWeb.LiliformLive @filters_all [ %{ title: "All", icon: "all", active: false, key: :all } ] def(mount(_params, _session, socket)) do items = items() filters = filters() socket = assign(socket, :opts, Keyword.get(unquote(opts), :opts, [])) socket = assign(socket, :items_all, items) socket = assign(socket, :selected_item, nil) socket = assign(socket, :details, nil) socket = assign( socket, :items, FreediveWeb.LiliformLive.filter( items, FreediveWeb.LiliformLive.get_active_filter(filters) ) ) socket = assign(socket, :filters, @filters_all ++ filters) socket = assign(socket, :query, "") if connected?(socket) do apply(__MODULE__, :on_mount_connected, [socket]) else {:ok, socket} end end def(handle_event("search", %{"value" => query}, socket)) do filtered_items = FreediveWeb.LiliformLive.filter( socket.assigns.items_all, FreediveWeb.LiliformLive.get_active_filter(socket.assigns.filters) ) searched_items = search(filtered_items, query) socket = assign(socket, :items, searched_items) socket = case Kernel.length(Map.keys(searched_items)) == 1 do true -> assign(socket, %{ selected_item: Map.get(searched_items, Map.keys(searched_items) |> List.first()), details: true }) false -> assign(socket, %{selected_item: nil, details: nil}) end {:noreply, socket} end def(handle_event("filter", %{"key" => key}, socket)) do key = String.to_existing_atom(key) items = FreediveWeb.LiliformLive.filter(socket.assigns.items_all, key) socket = assign(socket, :items, items) socket = assign( socket, :filters, FreediveWeb.LiliformLive.update_active_filter(socket.assigns.filters, key) ) {:noreply, socket} end def(handle_event("tap", %{"name" => item_name}, socket)) do # 1 tap to select, 2 taps for details item = socket.assigns.items[item_name] socket = case socket.assigns.selected_item do nil -> assign(socket, :selected_item, item) selected -> case selected.name == item_name do true -> case socket.assigns.details do nil -> assign(socket, :details, true) _ -> assign(socket, %{selected_item: item, details: nil}) end false -> assign(socket, %{selected_item: item, details: nil}) end end {:noreply, socket} end def(handle_params(params, _url, socket)) do socket = assign(socket, :query, Map.get(params, "search", "")) {:noreply, socket} end end end def filter(items, key) do case key do :all -> items _ -> items |> Enum.filter(fn {_name, item} -> item[key] == true end) |> Enum.into(%{}, fn {name, item} -> {name, item} end) end end def get_active_filter(filters) do filters |> Enum.find(& &1.active) |> Map.get(:key) end def update_active_filter(filters, key) do filters |> Enum.map(fn filter -> Map.put(filter, :active, filter.key == key) end) end end