Current section
Files
Jump to
Current section
Files
lib/phiaui_design/catalog/catalog_server.ex
defmodule PhiaUiDesign.Catalog.CatalogServer do
@moduledoc """
GenServer that caches introspected component data in ETS for fast access.
Refreshes on demand.
"""
use GenServer
alias PhiaUiDesign.Catalog.{Introspector, FamilyResolver}
@table :phiaui_design_catalog
# Client API
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
@doc "Get all components (cached)"
def all do
ensure_loaded()
case :ets.lookup(@table, :all_components) do
[{:all_components, components}] -> components
_ -> []
end
end
@doc "Get components grouped by tier"
def by_tier do
all()
|> Enum.group_by(& &1.tier)
|> Enum.sort_by(fn {tier, _} -> tier_order(tier) end)
end
@doc "Get a single component info"
def get(key) when is_atom(key) do
ensure_loaded()
case :ets.lookup(@table, {:component, key}) do
[{_, info}] -> info
_ -> nil
end
end
@doc "Get all families"
def families do
ensure_loaded()
case :ets.lookup(@table, :families) do
[{:families, fams}] -> fams
_ -> []
end
end
@doc "Force refresh the cache"
def refresh do
GenServer.call(__MODULE__, :refresh, 30_000)
end
@doc "Search components by name (fuzzy)"
def search(query) when is_binary(query) do
query_down = String.downcase(query)
all()
|> Enum.filter(fn comp ->
String.contains?(String.downcase(comp.name), query_down)
end)
end
# Server callbacks
@impl true
def init(_opts) do
table = :ets.new(@table, [:set, :public, :named_table, read_concurrency: true])
{:ok, %{table: table, loaded: false}}
end
@impl true
def handle_call(:refresh, _from, state) do
load_catalog()
{:reply, :ok, %{state | loaded: true}}
end
@impl true
def handle_call(:ensure_loaded, _from, %{loaded: true} = state) do
{:reply, :ok, state}
end
def handle_call(:ensure_loaded, _from, state) do
load_catalog()
{:reply, :ok, %{state | loaded: true}}
end
# Private
defp ensure_loaded do
if :ets.whereis(@table) == :undefined do
:ok
else
case :ets.lookup(@table, :loaded) do
[{:loaded, true}] -> :ok
_ -> GenServer.call(__MODULE__, :ensure_loaded, 30_000)
end
end
end
defp load_catalog do
components = Introspector.introspect_all()
families = FamilyResolver.resolve_families()
:ets.insert(@table, {:all_components, components})
:ets.insert(@table, {:families, families})
:ets.insert(@table, {:loaded, true})
# Index each component by key for fast lookup
for comp <- components do
:ets.insert(@table, {{:component, comp.key}, comp})
end
:ok
end
defp tier_order(:primitive), do: 0
defp tier_order(:interactive), do: 1
defp tier_order(:form), do: 2
defp tier_order(:navigation), do: 3
defp tier_order(:shell), do: 4
defp tier_order(:widget), do: 5
defp tier_order(:layout), do: 6
defp tier_order(:surface), do: 7
defp tier_order(:animation), do: 8
defp tier_order(_), do: 9
end