diff --git a/config/config.exs b/config/config.exs index 38658820..8531f177 100644 --- a/config/config.exs +++ b/config/config.exs @@ -60,15 +60,7 @@ config :dart_sass, :version, "1.54.5" config :tailwind, :version, "3.2.7" -config :wanderer_app, WandererApp.PromEx, - manual_metrics_start_delay: :no_delay, - metrics_server: [ - port: 4021, - path: "/metrics", - protocol: :http, - pool_size: 5, - cowboy_opts: [ip: {0, 0, 0, 0}] - ] +config :wanderer_app, WandererApp.PromEx, manual_metrics_start_delay: :no_delay config :wanderer_app, grafana_datasource_id: "wanderer" diff --git a/lib/wanderer_app/api/map.ex b/lib/wanderer_app/api/map.ex index 4ff84ac3..5aa35ae2 100644 --- a/lib/wanderer_app/api/map.ex +++ b/lib/wanderer_app/api/map.ex @@ -18,6 +18,7 @@ defmodule WandererApp.Api.Map do define(:update, action: :update) define(:update_acls, action: :update_acls) define(:update_hubs, action: :update_hubs) + define(:update_options, action: :update_options) define(:assign_owner, action: :assign_owner) define(:mark_as_deleted, action: :mark_as_deleted) @@ -112,6 +113,10 @@ defmodule WandererApp.Api.Map do accept [:hubs] end + update :update_options do + accept [:options] + end + update :mark_as_deleted do accept([]) @@ -167,6 +172,10 @@ defmodule WandererApp.Api.Map do allow_nil?(true) end + attribute :options, :string do + allow_nil? true + end + create_timestamp(:inserted_at) update_timestamp(:updated_at) end diff --git a/lib/wanderer_app/map.ex b/lib/wanderer_app/map.ex index 08f601ed..0a2adeb8 100644 --- a/lib/wanderer_app/map.ex +++ b/lib/wanderer_app/map.ex @@ -52,6 +52,15 @@ defmodule WandererApp.Map do end end + def get_map_options!(map) do + map + |> Map.get(:options) + |> case do + nil -> %{"layout" => "left_to_right"} + options -> Jason.decode!(options) + end + end + def update_map(map_id, map_update) do Cachex.get_and_update(:map_cache, map_id, fn map -> case map do diff --git a/lib/wanderer_app/map/map_position_calculator.ex b/lib/wanderer_app/map/map_position_calculator.ex index 04659cfc..b047e75d 100644 --- a/lib/wanderer_app/map/map_position_calculator.ex +++ b/lib/wanderer_app/map/map_position_calculator.ex @@ -19,46 +19,47 @@ defmodule WandererApp.Map.PositionCalculator do def get_system_bounding_rect(_system), do: [{0, 0}, {0, 0}] - def get_new_system_position(nil, rtree_name) do - {:ok, {x, y}} = rtree_name |> _check_system_available_positions(@start_x, @start_y, 1) + def get_new_system_position(nil, rtree_name, opts) do + {:ok, {x, y}} = rtree_name |> check_system_available_positions(@start_x, @start_y, 1, opts) %{x: x, y: y} end def get_new_system_position( %{position_x: start_x, position_y: start_y} = _old_system, - rtree_name + rtree_name, + opts ) do - {:ok, {x, y}} = rtree_name |> _check_system_available_positions(start_x, start_y, 1) + {:ok, {x, y}} = rtree_name |> check_system_available_positions(start_x, start_y, 1, opts) + %{x: x, y: y} end - defp _check_system_available_positions(_rtree_name, _start_x, _start_y, 100) do - {:ok, {@start_x, @start_y}} - end + defp check_system_available_positions(_rtree_name, _start_x, _start_y, 100, _opts), + do: {:ok, {@start_x, @start_y}} - defp _check_system_available_positions(rtree_name, start_x, start_y, level) do - possible_positions = _get_available_positions(level, start_x, start_y) + defp check_system_available_positions(rtree_name, start_x, start_y, level, opts) do + possible_positions = get_available_positions(level, start_x, start_y, opts) - case _get_available_position(possible_positions, rtree_name) do + case get_available_position(possible_positions, rtree_name) do {:ok, nil} -> - rtree_name |> _check_system_available_positions(start_x, start_y, level + 1) + rtree_name |> check_system_available_positions(start_x, start_y, level + 1, opts) {:ok, position} -> {:ok, position} end end - defp _get_available_position([], _rtree_name), do: {:ok, nil} + defp get_available_position([], _rtree_name), do: {:ok, nil} - defp _get_available_position([position | rest], rtree_name) do - if _is_available_position(position, rtree_name) do + defp get_available_position([position | rest], rtree_name) do + if is_available_position(position, rtree_name) do {:ok, position} else - _get_available_position(rest, rtree_name) + get_available_position(rest, rtree_name) end end - defp _is_available_position({x, y} = _position, rtree_name) do + defp is_available_position({x, y} = _position, rtree_name) do case DDRT.query(get_system_bounding_rect(%{position_x: x, position_y: y}), rtree_name) do {:ok, []} -> true @@ -71,9 +72,10 @@ defmodule WandererApp.Map.PositionCalculator do end end - def _get_available_positions(level, x, y), do: _adjusted_coordinates(1 + level * 2, x, y) + def get_available_positions(level, x, y, opts), + do: _adjusted_coordinates(1 + level * 2, x, y, opts) - defp _edge_coordinates(n) when n > 1 do + defp _edge_coordinates(n, opts) when n > 1 do min = -div(n, 2) max = div(n, 2) # Top edge @@ -90,16 +92,16 @@ defmodule WandererApp.Map.PositionCalculator do |> Enum.uniq() end - defp _sorted_edge_coordinates(n) when n > 1 do - coordinates = _edge_coordinates(n) + defp _sorted_edge_coordinates(n, opts) when n > 1 do + coordinates = _edge_coordinates(n, opts) middle_right_index = div(n, 2) Enum.slice(coordinates, middle_right_index, length(coordinates) - middle_right_index) ++ Enum.slice(coordinates, 0, middle_right_index) end - defp _adjusted_coordinates(n, start_x, start_y) when n > 1 do - sorted_coords = _sorted_edge_coordinates(n) + defp _adjusted_coordinates(n, start_x, start_y, opts) when n > 1 do + sorted_coords = _sorted_edge_coordinates(n, opts) Enum.map(sorted_coords, fn {x, y} -> { diff --git a/lib/wanderer_app/map/map_server_impl.ex b/lib/wanderer_app/map/map_server_impl.ex index 24d2d300..07efb5c2 100644 --- a/lib/wanderer_app/map/map_server_impl.ex +++ b/lib/wanderer_app/map/map_server_impl.ex @@ -11,7 +11,8 @@ defmodule WandererApp.Map.Server.Impl do defstruct [ :map_id, :rtree_name, - map: nil + map: nil, + map_opts: [] ] # @ccp1 -1 @@ -834,12 +835,12 @@ defmodule WandererApp.Map.Server.Impl do character_id, location, old_location, - %{map: map, map_id: map_id, rtree_name: rtree_name} = _state + %{map: map, map_id: map_id, rtree_name: rtree_name, map_opts: map_opts} = _state ) do case is_nil(old_location.solar_system_id) and _can_add_location(map.scope, location.solar_system_id) do true -> - :ok = maybe_add_system(map_id, location, nil, rtree_name) + :ok = maybe_add_system(map_id, location, nil, rtree_name, map_opts) _ -> case _is_connection_valid( @@ -849,8 +850,8 @@ defmodule WandererApp.Map.Server.Impl do ) do true -> {:ok, character} = WandererApp.Character.get_character(character_id) - :ok = maybe_add_system(map_id, location, old_location, rtree_name) - :ok = maybe_add_system(map_id, old_location, location, rtree_name) + :ok = maybe_add_system(map_id, location, old_location, rtree_name, map_opts) + :ok = maybe_add_system(map_id, old_location, location, rtree_name, map_opts) :ok = maybe_add_connection(map_id, location, old_location, character) _ -> @@ -1268,7 +1269,12 @@ defmodule WandererApp.Map.Server.Impl do |> WandererApp.Map.add_connections!(connections) |> WandererApp.Map.add_characters!(characters) - %{state | map: map} + map_options = WandererApp.Map.get_map_options!(map) + map_opts = [ + layout: map_options |> Map.get("layout") + ] + + %{state | map: map, map_opts: map_opts} end defp _init_map_systems(state, [] = _systems), do: state @@ -1614,11 +1620,11 @@ defmodule WandererApp.Map.Server.Impl do defp maybe_add_connection(_map_id, _location, _old_location, _character), do: :ok - defp maybe_add_system(map_id, location, old_location, rtree_name) + defp maybe_add_system(map_id, location, old_location, rtree_name, opts) when not is_nil(location) do case WandererApp.Map.check_location(map_id, location) do {:ok, location} -> - {:ok, position} = calc_new_system_position(map_id, old_location, rtree_name) + {:ok, position} = calc_new_system_position(map_id, old_location, rtree_name, opts) case WandererApp.MapSystemRepo.get_by_map_and_solar_system_id( map_id, @@ -1688,13 +1694,13 @@ defmodule WandererApp.Map.Server.Impl do end end - defp maybe_add_system(_map_id, _location, _old_location, _rtree_name), do: :ok + defp maybe_add_system(_map_id, _location, _old_location, _rtree_name, _opts), do: :ok - defp calc_new_system_position(map_id, old_location, rtree_name) do + defp calc_new_system_position(map_id, old_location, rtree_name, opts) do {:ok, map_id |> WandererApp.Map.find_system_by_location(old_location) - |> WandererApp.Map.PositionCalculator.get_new_system_position(rtree_name)} + |> WandererApp.Map.PositionCalculator.get_new_system_position(rtree_name, opts)} end defp _broadcast_acl_updates( diff --git a/lib/wanderer_app_web/live/maps/maps_live.ex b/lib/wanderer_app_web/live/maps/maps_live.ex index 3552f876..1d82f8d1 100644 --- a/lib/wanderer_app_web/live/maps/maps_live.ex +++ b/lib/wanderer_app_web/live/maps/maps_live.ex @@ -112,6 +112,13 @@ defmodule WandererAppWeb.MapsLive do "auto_renew?" => true } + options_form = + map.options + |> case do + nil -> %{"layout" => "left_to_right"} + options -> Jason.decode!(options) + end + {:ok, estimated_price, discount} = WandererApp.Map.SubscriptionManager.estimate_price(subscription_form, false) @@ -130,6 +137,7 @@ defmodule WandererAppWeb.MapsLive do active_settings_tab: "general", is_adding_subscription?: false, selected_subscription: nil, + options_form: options_form |> to_form(), map_subscriptions: map_subscriptions, subscription_form: subscription_form |> to_form(), estimated_price: estimated_price, @@ -142,6 +150,10 @@ defmodule WandererAppWeb.MapsLive do {"3 Months", "3"}, {"6 Months", "6"}, {"1 Year", "12"} + ], + layout_options: [ + {"Left To Right", "left_to_right"}, + {"Top To Bottom", "top_to_bottom"} ] ) |> allow_upload(:settings, @@ -653,6 +665,22 @@ defmodule WandererAppWeb.MapsLive do |> push_patch(to: ~p"/maps")} end + def handle_event( + "update_options", + %{ + "layout" => layout + } = options_form, + %{assigns: %{map_id: map_id, map: map, current_user: current_user}} = socket + ) do + options = %{layout: layout} + + updated_map = + map + |> WandererApp.Api.Map.update_options!(%{options: Jason.encode!(options)}) + + {:noreply, socket |> assign(map: updated_map, options_form: options_form)} + end + @impl true def handle_event("noop", _, socket) do {:noreply, socket} diff --git a/lib/wanderer_app_web/live/maps/maps_live.html.heex b/lib/wanderer_app_web/live/maps/maps_live.html.heex index cd394afe..4b66e7c8 100644 --- a/lib/wanderer_app_web/live/maps/maps_live.html.heex +++ b/lib/wanderer_app_web/live/maps/maps_live.html.heex @@ -26,7 +26,6 @@ >
-
<.icon name="hero-wrench-screwdriver-solid" class="w-4 h-4" /> General + + <.icon name="hero-wrench-screwdriver-solid" class="w-4 h-4" /> Import/Export +
<.header :if={@active_settings_tab == "general"} class="bordered border-1 border-zinc-800"> + <:actions> + <.form + :let={f} + :if={assigns |> Map.get(:options_form, false)} + for={@options_form} + phx-change="update_options" + > +
Map systems layout
+
+ <.input + type="select" + field={f[:layout]} + class="p-dropdown p-component p-inputwrapper" + placeholder="Map default layout" + options={@layout_options} + /> +
+ + + + <.header :if={@active_settings_tab == "import"} class="bordered border-1 border-zinc-800"> Import/Export Map Settings <:actions> <.form :if={assigns |> Map.get(:import_form, false)} for={@import_form} phx-change="import"> diff --git a/priv/repo/migrations/20241006092351_add_map_options.exs b/priv/repo/migrations/20241006092351_add_map_options.exs new file mode 100644 index 00000000..2e487aa2 --- /dev/null +++ b/priv/repo/migrations/20241006092351_add_map_options.exs @@ -0,0 +1,21 @@ +defmodule WandererApp.Repo.Migrations.AddMapOptions do + @moduledoc """ + Updates resources based on their most recent snapshots. + + This file was autogenerated with `mix ash_postgres.generate_migrations` + """ + + use Ecto.Migration + + def up do + alter table(:maps_v1) do + add :options, :text + end + end + + def down do + alter table(:maps_v1) do + remove :options + end + end +end diff --git a/priv/resource_snapshots/repo/maps_v1/20241006092351.json b/priv/resource_snapshots/repo/maps_v1/20241006092351.json new file mode 100644 index 00000000..72b18296 --- /dev/null +++ b/priv/resource_snapshots/repo/maps_v1/20241006092351.json @@ -0,0 +1,186 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "primary_key?": true, + "references": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "name", + "type": "text" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "slug", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "description", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "personal_note", + "type": "text" + }, + { + "allow_nil?": true, + "default": "[]", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "hubs", + "type": [ + "array", + "text" + ] + }, + { + "allow_nil?": false, + "default": "\"wormholes\"", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "scope", + "type": "text" + }, + { + "allow_nil?": true, + "default": "false", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "deleted", + "type": "boolean" + }, + { + "allow_nil?": true, + "default": "false", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "only_tracked_characters", + "type": "boolean" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "options", + "type": "text" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "inserted_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "updated_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "maps_v1_owner_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "character_v1" + }, + "size": null, + "source": "owner_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "E5FC6B5F1B9AD5E23163494C7C93A8002F9C812AFC7A26A8C33A344877086A03", + "identities": [ + { + "all_tenants?": false, + "base_filter": null, + "index_name": "maps_v1_unique_slug_index", + "keys": [ + { + "type": "atom", + "value": "slug" + } + ], + "name": "unique_slug", + "nils_distinct?": true, + "where": null + } + ], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.WandererApp.Repo", + "schema": null, + "table": "maps_v1" +} \ No newline at end of file