diff --git a/README.md b/README.md index 316a517..644fa93 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Rocketsized -[![Rocketsized landscape portrait](/assets/poster_landscape.jpg)](https://rocketsized.com) +[![Rocketsized landscape portrait](/priv/static/images/poster_landscape.jpg)](https://rocketsized.com) A small hobby project to list and compare various launch vehicles against one another. I've always seen those rockets lined up side by side but its always a one-off thing, I wanted an automated tool that could do that for any combination of rockets. diff --git a/lib/rocketsized/rocket.ex b/lib/rocketsized/rocket.ex index 2e63b1b..406b5a3 100644 --- a/lib/rocketsized/rocket.ex +++ b/lib/rocketsized/rocket.ex @@ -640,7 +640,7 @@ defmodule Rocketsized.Rocket do end @spec search_slugs(list(String.t())) :: list(SearchSlug.t()) - def search_slugs(slugs) do + def search_slugs([_ | _] = slugs) do groups = slugs |> Enum.map(&SearchSlug.Type.load!(&1)) @@ -653,6 +653,8 @@ defmodule Rocketsized.Rocket do from(s in SearchSlug, where: ^groups, limit: 20) |> Repo.all() end + def search_slugs(_), do: [] + def list_vehicles_image_meta() do from(v in Vehicle, select: %{ diff --git a/lib/rocketsized_web/components/layouts/root.html.heex b/lib/rocketsized_web/components/layouts/root.html.heex index 22d33b3..a81fa99 100644 --- a/lib/rocketsized_web/components/layouts/root.html.heex +++ b/lib/rocketsized_web/components/layouts/root.html.heex @@ -6,6 +6,10 @@ + + + + <.live_title suffix=" ยท Rocketsized"> <%= assigns[:page_title] %> diff --git a/lib/rocketsized_web/controllers/render_controller.ex b/lib/rocketsized_web/controllers/render_controller.ex index 9732639..62e6bd3 100644 --- a/lib/rocketsized_web/controllers/render_controller.ex +++ b/lib/rocketsized_web/controllers/render_controller.ex @@ -3,17 +3,18 @@ defmodule RocketsizedWeb.RenderController do alias Rocketsized.Rocket alias Rocketsized.Rocket.Render.Image + alias RocketsizedWeb.FilterParams def render(conn, %{"type" => type} = params) when type in ["portrait", "landscape", "wallpaper"] do - {:ok, flop} = Flop.validate(params, for: Rocket.Vehicle) + {:ok, flop} = Flop.validate(FilterParams.load_params(params), for: Rocket.Vehicle) render = Rocket.flop_render_find_or_create(flop, to_type(type)) send_download(conn, {:file, Image.storage_file_path({render.image, render})}) end def preview(conn, %{"type" => type} = params) when type in ["portrait", "landscape", "wallpaper"] do - {:ok, flop} = Flop.validate(params, for: Rocket.Vehicle) + {:ok, flop} = Flop.validate(FilterParams.load_params(params), for: Rocket.Vehicle) conn |> put_resp_content_type("image/svg+xml") diff --git a/lib/rocketsized_web/filter_params.ex b/lib/rocketsized_web/filter_params.ex index ad2f3e4..9afd8c5 100644 --- a/lib/rocketsized_web/filter_params.ex +++ b/lib/rocketsized_web/filter_params.ex @@ -1,7 +1,19 @@ defmodule RocketsizedWeb.FilterParams do + @moduledoc """ + This module handles simplifying the params used by `Flop`. + Since `Flop` produces the equivalent of + ```elixir + %{"filters", [%{"field" => "search", "op" => "in", "value" => ["r_1", "r2"]}])} + ``` + we want to compress it so its a bit more user friendly when used in filtering or render endpoints + """ alias Rocketsized.Rocket.SearchSlug.Type alias Rocketsized.Rocket.SearchSlug + @doc """ + Modify a flop by adding a slug item to its search filter. + This is used to modify the flop on the fly to construct addition links + """ def add(%Flop{} = flop, %SearchSlug{} = search_slug) do Map.update!(flop, :filters, fn filters -> search = Flop.Filter.get_value(filters, :search) || [] @@ -9,6 +21,10 @@ defmodule RocketsizedWeb.FilterParams do end) end + @doc """ + Modify a flop by adding a slug item to its search filter. + This is used to modify the flop on the fly to construct removal links + """ def remove(%Flop{} = flop, %SearchSlug{} = search_slug) do Map.update!(flop, :filters, fn filters -> search = Flop.Filter.get_value(filters, :search) || [] @@ -16,9 +32,12 @@ defmodule RocketsizedWeb.FilterParams do end) end + @doc """ + Convert the :search `Flop.Filter` into a user friendly form, simplifying the information as much as possible for the url + """ def dump_params(params) do - {search, params} = - Keyword.get_and_update(params, :filters, &Flop.Filter.pop_value(&1, :search)) + {filters, params} = Keyword.pop(params, :filters, []) + {search, _other_filters} = Flop.Filter.pop_value(filters, :search) (params ++ (search @@ -28,6 +47,9 @@ defmodule RocketsizedWeb.FilterParams do |> Enum.sort_by(fn {group, _slugs} -> group end) end + @doc """ + Convert the simplified version from `dump_params` into the standard `Flop` filters + """ def load_params(params) do countries = Map.get(params, "country", []) |> Enum.map(&%SearchSlug{type: :country, slug: &1}) rockets = Map.get(params, "rocket", []) |> Enum.map(&%SearchSlug{type: :rocket, slug: &1}) diff --git a/lib/rocketsized_web/live/rocketgrid_live/filter_component.ex b/lib/rocketsized_web/live/rocketgrid_live/filter_component.ex index 2c60e07..f09ac60 100644 --- a/lib/rocketsized_web/live/rocketgrid_live/filter_component.ex +++ b/lib/rocketsized_web/live/rocketgrid_live/filter_component.ex @@ -116,12 +116,7 @@ defmodule RocketsizedWeb.RocketgridLive.FilterComponent do @impl true def update(%{id: id, class: class, flop: %Flop{filters: filters} = flop}, socket) do - items = - case Flop.Filter.get_value(filters, :search) do - [_ | _] = search -> Rocket.search_slugs(search) - _ -> [] - end - + items = Rocket.search_slugs(Flop.Filter.get_value(filters, :search) || []) {:ok, socket |> assign(id: id, options: [], items: items, flop: flop, class: class)} end diff --git a/lib/rocketsized_web/live/rocketgrid_live/index.ex b/lib/rocketsized_web/live/rocketgrid_live/index.ex index ddacb3b..e73cff1 100644 --- a/lib/rocketsized_web/live/rocketgrid_live/index.ex +++ b/lib/rocketsized_web/live/rocketgrid_live/index.ex @@ -45,4 +45,11 @@ defmodule RocketsizedWeb.RocketgridLive.Index do defp direction(_to = "next"), do: :next defp direction(_to = "previous"), do: :previous + + defp render_build_path(%Flop{} = flop, type) do + Flop.Phoenix.build_path( + &~p"/render/#{type}?#{FilterParams.dump_params(&1)}", + %{flop | first: nil, last: nil} |> Flop.reset_order() |> Flop.reset_cursors() + ) + end end diff --git a/lib/rocketsized_web/live/rocketgrid_live/index.html.heex b/lib/rocketsized_web/live/rocketgrid_live/index.html.heex index bd4198c..8ac2ebc 100644 --- a/lib/rocketsized_web/live/rocketgrid_live/index.html.heex +++ b/lib/rocketsized_web/live/rocketgrid_live/index.html.heex @@ -101,21 +101,21 @@

<.link - navigate={Flop.Phoenix.build_path("/render/portrait", @meta)} + navigate={render_build_path(@meta.flop, "portrait")} class="rounded-md bg-white px-2.5 py-1.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50" > Portrait Poster <.icon class="h-4 w-4 align-text-bottom" name="hero-document-arrow-down" /> <.link - navigate={Flop.Phoenix.build_path("/render/landscape", @meta)} + navigate={render_build_path(@meta.flop, "landscape")} class="rounded-md bg-white px-2.5 py-1.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50" > Landscape Poster <.icon class="h-4 w-4 align-text-bottom" name="hero-document-arrow-down" /> <.link - navigate={Flop.Phoenix.build_path("/render/wallpaper", @meta)} + navigate={render_build_path(@meta.flop, "wallpaper")} class="rounded-md bg-white px-2.5 py-1.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50" > 1080p Wallpaper diff --git a/assets/poster_landscape.jpg b/priv/static/images/poster_landscape.jpg similarity index 100% rename from assets/poster_landscape.jpg rename to priv/static/images/poster_landscape.jpg diff --git a/test/rocketsized_web/filter_params_test.exs b/test/rocketsized_web/filter_params_test.exs new file mode 100644 index 0000000..c6d58b1 --- /dev/null +++ b/test/rocketsized_web/filter_params_test.exs @@ -0,0 +1,31 @@ +defmodule RocketsizedWeb.FilterParamsTest do + use Rocketsized.DataCase + + alias RocketsizedWeb.FilterParams + + describe "FilterParams" do + test "should load and dump params" do + pairs = [ + {["c_ca"], [country: ["ca"]]}, + {["r_fh", "r_f9"], [rocket: ["fh", "f9"]]}, + {["o_spacex"], [org: ["spacex"]]}, + {["r_fh", "r_f9", "o_spacex"], [org: ["spacex"], rocket: ["fh", "f9"]]}, + {["c_ca", "o_spacex"], [country: ["ca"], org: ["spacex"]]}, + {["c_ca", "r_fh", "r_f9"], [country: ["ca"], rocket: ["fh", "f9"]]}, + {["c_ca", "r_fh", "r_f9", "o_spacex"], + [country: ["ca"], org: ["spacex"], rocket: ["fh", "f9"]]} + ] + + for {full, simple} <- pairs do + dumped = + FilterParams.dump_params(filters: [%Flop.Filter{field: :search, op: :in, value: full}]) + + assert dumped == simple + + http_transmitted_data = simple |> Map.new() |> Jason.encode!() |> Jason.decode!() + loaded = FilterParams.load_params(http_transmitted_data) + assert loaded["filters"] == [%{"field" => "search", "op" => "in", "value" => full}] + end + end + end +end