From 696c7d2cd15d0607039bfecbc01bd28d7b1324ea Mon Sep 17 00:00:00 2001 From: Dmitry Popov Date: Fri, 1 Nov 2024 14:54:34 +0400 Subject: [PATCH] Map events refactoring (#41) * refactor(Map): Map event handling refactoring --- .../SystemCustomLabelDialog.tsx | 2 +- .../SystemSettingsDialog.tsx | 2 +- config/runtime.exs | 4 +- lib/wanderer_app.ex | 2 + .../map/map_position_calculator.ex | 2 +- lib/wanderer_app/map/map_server_impl.ex | 29 +- .../repositories/map_system_repo.ex | 6 +- .../components/map/map_picker.ex | 3 + .../map_activity_event_handler.ex | 49 + .../map_characters_event_handler.ex | 388 +++ .../map_connections_event_handler.ex | 197 ++ .../event_handlers/map_core_event_handler.ex | 544 +++++ .../map_routes_event_handler.ex | 131 ++ .../map_signatures_event_handler.ex | 350 +++ .../map_systems_event_handler.ex | 326 +++ .../live/maps/map_event_handler.ex | 292 +++ lib/wanderer_app_web/live/maps/map_live.ex | 2082 +---------------- .../live/maps/map_live.html.heex | 24 +- lib/wanderer_app_web/live/maps/maps_live.ex | 19 +- 19 files changed, 2337 insertions(+), 2115 deletions(-) create mode 100644 lib/wanderer_app_web/live/maps/event_handlers/map_activity_event_handler.ex create mode 100644 lib/wanderer_app_web/live/maps/event_handlers/map_characters_event_handler.ex create mode 100644 lib/wanderer_app_web/live/maps/event_handlers/map_connections_event_handler.ex create mode 100644 lib/wanderer_app_web/live/maps/event_handlers/map_core_event_handler.ex create mode 100644 lib/wanderer_app_web/live/maps/event_handlers/map_routes_event_handler.ex create mode 100644 lib/wanderer_app_web/live/maps/event_handlers/map_signatures_event_handler.ex create mode 100644 lib/wanderer_app_web/live/maps/event_handlers/map_systems_event_handler.ex create mode 100644 lib/wanderer_app_web/live/maps/map_event_handler.ex diff --git a/assets/js/hooks/Mapper/components/mapInterface/components/SystemCustomLabelDialog/SystemCustomLabelDialog.tsx b/assets/js/hooks/Mapper/components/mapInterface/components/SystemCustomLabelDialog/SystemCustomLabelDialog.tsx index 7748c84a..5a137842 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/components/SystemCustomLabelDialog/SystemCustomLabelDialog.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/components/SystemCustomLabelDialog/SystemCustomLabelDialog.tsx @@ -79,7 +79,7 @@ export const SystemCustomLabelDialog = ({ systemId, visible, setVisible }: Syste // @ts-ignore const handleInput = useCallback(e => { - e.target.value = e.target.value.toUpperCase().replace(/[^A-Z0-9[\](){}]/g, ''); + e.target.value = e.target.value.toUpperCase().replace(/[^A-Z0-9\-[\](){}]/g, ''); }, []); return ( diff --git a/assets/js/hooks/Mapper/components/mapInterface/components/SystemSettingsDialog/SystemSettingsDialog.tsx b/assets/js/hooks/Mapper/components/mapInterface/components/SystemSettingsDialog/SystemSettingsDialog.tsx index 7ceddf4c..439f31f1 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/components/SystemSettingsDialog/SystemSettingsDialog.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/components/SystemSettingsDialog/SystemSettingsDialog.tsx @@ -90,7 +90,7 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe }, []); const handleInput = useCallback((e: any) => { - e.target.value = e.target.value.toUpperCase().replace(/[^A-Z0-9[\](){}]/g, ''); + e.target.value = e.target.value.toUpperCase().replace(/[^A-Z0-9\-[\](){}]/g, ''); }, []); return ( diff --git a/config/runtime.exs b/config/runtime.exs index 79c51c0a..b662189c 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -55,11 +55,11 @@ map_subscriptions_enabled = map_subscription_characters_limit = config_dir - |> get_int_from_path_or_env("WANDERER_MAP_SUBSCRIPTION_CHARACTERS_LIMIT", 100) + |> get_int_from_path_or_env("WANDERER_MAP_SUBSCRIPTION_CHARACTERS_LIMIT", 10_000) map_subscription_hubs_limit = config_dir - |> get_int_from_path_or_env("WANDERER_MAP_SUBSCRIPTION_HUBS_LIMIT", 10) + |> get_int_from_path_or_env("WANDERER_MAP_SUBSCRIPTION_HUBS_LIMIT", 100) wallet_tracking_enabled = config_dir diff --git a/lib/wanderer_app.ex b/lib/wanderer_app.ex index 98744b68..f5f26d14 100644 --- a/lib/wanderer_app.ex +++ b/lib/wanderer_app.ex @@ -7,6 +7,8 @@ defmodule WandererApp do if it comes from the database, an external API or others. """ + require Logger + @doc """ When used, dispatch to the appropriate domain service """ diff --git a/lib/wanderer_app/map/map_position_calculator.ex b/lib/wanderer_app/map/map_position_calculator.ex index 3aac7264..f16d6a6f 100644 --- a/lib/wanderer_app/map/map_position_calculator.ex +++ b/lib/wanderer_app/map/map_position_calculator.ex @@ -75,7 +75,7 @@ defmodule WandererApp.Map.PositionCalculator do def get_available_positions(level, x, y, opts), do: adjusted_coordinates(1 + level * 2, x, y, opts) - defp edge_coordinates(n, opts) when n > 1 do + defp edge_coordinates(n, _opts) when n > 1 do min = -div(n, 2) max = div(n, 2) # Top edge diff --git a/lib/wanderer_app/map/map_server_impl.ex b/lib/wanderer_app/map/map_server_impl.ex index 6c7f0719..e015c404 100644 --- a/lib/wanderer_app/map/map_server_impl.ex +++ b/lib/wanderer_app/map/map_server_impl.ex @@ -193,7 +193,9 @@ defmodule WandererApp.Map.Server.Impl do :ok else - {:error, _error} -> + _error -> + {:ok, character} = WandererApp.Character.get_character(character_id) + broadcast!(map_id, :character_added, character) :ok end end) @@ -806,7 +808,7 @@ defmodule WandererApp.Map.Server.Impl do } end - def handle_event({:options_updated, options}, %{map: map, map_id: map_id} = state), + def handle_event({:options_updated, options}, state), do: %{ state | map_opts: [ @@ -1164,11 +1166,10 @@ defmodule WandererApp.Map.Server.Impl do rtree_name ) - {:ok, - existing_system - |> WandererApp.MapSystemRepo.update_position!(%{position_x: x, position_y: y}) - |> WandererApp.MapSystemRepo.cleanup_labels(map_opts) - |> WandererApp.MapSystemRepo.cleanup_tags()} + existing_system + |> WandererApp.MapSystemRepo.update_position!(%{position_x: x, position_y: y}) + |> WandererApp.MapSystemRepo.cleanup_labels!(map_opts) + |> WandererApp.MapSystemRepo.cleanup_tags() end _ -> @@ -1211,8 +1212,6 @@ defmodule WandererApp.Map.Server.Impl do solar_system_id: solar_system_id }) - :telemetry.execute([:wanderer_app, :map, :system, :add], %{count: 1}) - state end @@ -1683,11 +1682,11 @@ defmodule WandererApp.Map.Server.Impl do defp maybe_add_connection(_map_id, _location, _old_location, _character_id), do: :ok - defp maybe_add_system(map_id, location, old_location, rtree_name, opts) + defp maybe_add_system(map_id, location, old_location, rtree_name, map_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, opts) + {:ok, position} = calc_new_system_position(map_id, old_location, rtree_name, map_opts) case WandererApp.MapSystemRepo.get_by_map_and_solar_system_id( map_id, @@ -1696,10 +1695,12 @@ defmodule WandererApp.Map.Server.Impl do {:ok, existing_system} when not is_nil(existing_system) -> {:ok, updated_system} = existing_system - |> WandererApp.MapSystemRepo.update_position(%{ + |> WandererApp.MapSystemRepo.update_position!(%{ position_x: position.x, position_y: position.y }) + |> WandererApp.MapSystemRepo.cleanup_labels!(map_opts) + |> WandererApp.MapSystemRepo.cleanup_tags() @ddrt.insert( {existing_system.solar_system_id, @@ -1721,7 +1722,7 @@ defmodule WandererApp.Map.Server.Impl do _ -> {:ok, solar_system_info} = - WandererApp.Api.MapSolarSystem.by_solar_system_id(location.solar_system_id) + WandererApp.CachedInfo.get_system_static_info(location.solar_system_id) WandererApp.MapSystemRepo.create(%{ map_id: map_id, @@ -1757,7 +1758,7 @@ defmodule WandererApp.Map.Server.Impl do end end - defp maybe_add_system(_map_id, _location, _old_location, _rtree_name, _opts), do: :ok + defp maybe_add_system(_map_id, _location, _old_location, _rtree_name, _map_opts), do: :ok defp calc_new_system_position(map_id, old_location, rtree_name, opts), do: diff --git a/lib/wanderer_app/repositories/map_system_repo.ex b/lib/wanderer_app/repositories/map_system_repo.ex index c3f3bad0..dce532cf 100644 --- a/lib/wanderer_app/repositories/map_system_repo.ex +++ b/lib/wanderer_app/repositories/map_system_repo.ex @@ -33,7 +33,7 @@ defmodule WandererApp.MapSystemRepo do {:error, error} end - def cleanup_labels(%{labels: labels} = system, opts) do + def cleanup_labels!(%{labels: labels} = system, opts) do store_custom_labels? = Keyword.get(opts, :store_custom_labels, "false") |> String.to_existing_atom() @@ -47,7 +47,7 @@ defmodule WandererApp.MapSystemRepo do def cleanup_tags(system) do system - |> WandererApp.Api.MapSystem.update_tag!(%{ + |> WandererApp.Api.MapSystem.update_tag(%{ tag: nil }) end @@ -56,7 +56,7 @@ defmodule WandererApp.MapSystemRepo do labels |> Jason.decode!() |> case do - %{"customLabel" => customLabel} = labels when is_binary(customLabel) -> + %{"customLabel" => customLabel} when is_binary(customLabel) -> %{"customLabel" => customLabel, "labels" => []} |> Jason.encode!() diff --git a/lib/wanderer_app_web/components/map/map_picker.ex b/lib/wanderer_app_web/components/map/map_picker.ex index ac63e091..841c1a75 100644 --- a/lib/wanderer_app_web/components/map/map_picker.ex +++ b/lib/wanderer_app_web/components/map/map_picker.ex @@ -12,6 +12,7 @@ defmodule WandererAppWeb.MapPicker do {:ok, socket} end + @impl true def update( %{ current_user: current_user, @@ -29,6 +30,7 @@ defmodule WandererAppWeb.MapPicker do end)} end + @impl true def render(assigns) do ~H"""
@@ -56,6 +58,7 @@ defmodule WandererAppWeb.MapPicker do """ end + @impl true def handle_event("select", %{"map_slug" => map_slug} = _params, socket) do notify_to(socket.assigns.notify_to, socket.assigns.event_name, map_slug) diff --git a/lib/wanderer_app_web/live/maps/event_handlers/map_activity_event_handler.ex b/lib/wanderer_app_web/live/maps/event_handlers/map_activity_event_handler.ex new file mode 100644 index 00000000..8283beb4 --- /dev/null +++ b/lib/wanderer_app_web/live/maps/event_handlers/map_activity_event_handler.ex @@ -0,0 +1,49 @@ +defmodule WandererAppWeb.MapActivityEventHandler do + use WandererAppWeb, :live_component + use Phoenix.Component + require Logger + + alias WandererAppWeb.{MapEventHandler, MapCoreEventHandler} + + def handle_server_event( + %{ + event: :character_activity, + payload: character_activity + }, + socket + ), + do: socket |> assign(:character_activity, character_activity) + + def handle_server_event(event, socket), + do: MapCoreEventHandler.handle_server_event(event, socket) + + def handle_ui_event("show_activity", _, %{assigns: %{map_id: map_id}} = socket) do + Task.async(fn -> + {:ok, character_activity} = map_id |> get_character_activity() + + {:character_activity, character_activity} + end) + + {:noreply, + socket + |> assign(:show_activity?, true)} + end + + def handle_ui_event("hide_activity", _, socket), + do: {:noreply, socket |> assign(show_activity?: false)} + + def handle_ui_event(event, body, socket), + do: MapCoreEventHandler.handle_ui_event(event, body, socket) + + defp get_character_activity(map_id) do + {:ok, jumps} = WandererApp.Api.MapChainPassages.by_map_id(%{map_id: map_id}) + + jumps = + jumps + |> Enum.map(fn p -> + %{p | character: p.character |> MapEventHandler.map_ui_character_stat()} + end) + + {:ok, %{jumps: jumps}} + end +end diff --git a/lib/wanderer_app_web/live/maps/event_handlers/map_characters_event_handler.ex b/lib/wanderer_app_web/live/maps/event_handlers/map_characters_event_handler.ex new file mode 100644 index 00000000..63099b6d --- /dev/null +++ b/lib/wanderer_app_web/live/maps/event_handlers/map_characters_event_handler.ex @@ -0,0 +1,388 @@ +defmodule WandererAppWeb.MapCharactersEventHandler do + use WandererAppWeb, :live_component + use Phoenix.Component + require Logger + + alias WandererAppWeb.{MapEventHandler, MapCoreEventHandler} + + def handle_server_event(%{event: :character_added, payload: character}, socket) do + socket + |> MapEventHandler.push_map_event( + "character_added", + character |> map_ui_character() + ) + end + + def handle_server_event(%{event: :character_removed, payload: character}, socket) do + socket + |> MapEventHandler.push_map_event( + "character_removed", + character |> map_ui_character() + ) + end + + def handle_server_event(%{event: :character_updated, payload: character}, socket) do + socket + |> MapEventHandler.push_map_event( + "character_updated", + character |> map_ui_character() + ) + end + + def handle_server_event( + %{event: :characters_updated}, + %{ + assigns: %{ + map_id: map_id + } + } = socket + ) do + characters = + map_id + |> WandererApp.Map.list_characters() + |> Enum.map(&map_ui_character/1) + + socket + |> MapEventHandler.push_map_event( + "characters_updated", + characters + ) + end + + def handle_server_event( + %{event: :present_characters_updated, payload: present_character_eve_ids}, + socket + ), + do: + socket + |> MapEventHandler.push_map_event( + "present_characters", + present_character_eve_ids + ) + + def handle_server_event(event, socket), + do: MapCoreEventHandler.handle_server_event(event, socket) + + def handle_ui_event( + "add_character", + _, + %{ + assigns: %{ + current_user: current_user, + map_id: map_id, + user_permissions: %{track_character: true} + } + } = socket + ) do + {:ok, character_settings} = + case WandererApp.Api.MapCharacterSettings.read_by_map(%{map_id: map_id}) do + {:ok, settings} -> {:ok, settings} + _ -> {:ok, []} + end + + {:noreply, + socket + |> assign( + show_tracking?: true, + character_settings: character_settings + ) + |> assign_async(:characters, fn -> + {:ok, map} = + map_id + |> WandererApp.MapRepo.get([:acls]) + + map + |> WandererApp.Maps.load_characters( + character_settings, + current_user.id + ) + end)} + end + + def handle_ui_event( + "add_character", + _, + %{ + assigns: %{ + user_permissions: %{track_character: false} + } + } = socket + ), + do: + {:noreply, + socket + |> put_flash( + :error, + "You don't have permissions to track characters. Please contact administrator." + )} + + def handle_ui_event( + "toggle_track", + %{"character-id" => character_id}, + %{ + assigns: %{ + map_id: map_id, + character_settings: character_settings, + current_user: current_user, + only_tracked_characters: only_tracked_characters + } + } = socket + ) do + socket = + case character_settings |> Enum.find(&(&1.character_id == character_id)) do + nil -> + {:ok, map_character_settings} = + WandererApp.Api.MapCharacterSettings.create(%{ + character_id: character_id, + map_id: map_id, + tracked: true + }) + + character = map_character_settings |> Ash.load!(:character) |> Map.get(:character) + + :ok = track_characters([character], map_id, true) + :ok = add_characters([character], map_id, true) + + socket + + character_setting -> + case character_setting.tracked do + true -> + {:ok, map_character_settings} = + character_setting + |> WandererApp.Api.MapCharacterSettings.untrack() + + character = map_character_settings |> Ash.load!(:character) |> Map.get(:character) + + :ok = untrack_characters([character], map_id) + :ok = remove_characters([character], map_id) + + if only_tracked_characters do + Process.send_after(self(), :not_all_characters_tracked, 10) + end + + socket + + _ -> + {:ok, map_character_settings} = + character_setting + |> WandererApp.Api.MapCharacterSettings.track() + + character = map_character_settings |> Ash.load!(:character) |> Map.get(:character) + + :ok = track_characters([character], map_id, true) + :ok = add_characters([character], map_id, true) + + socket + end + end + + %{result: characters} = socket.assigns.characters + + {:ok, map_characters} = get_tracked_map_characters(map_id, current_user) + + user_character_eve_ids = map_characters |> Enum.map(& &1.eve_id) + + {:ok, character_settings} = + case WandererApp.Api.MapCharacterSettings.read_by_map(%{map_id: map_id}) do + {:ok, settings} -> {:ok, settings} + _ -> {:ok, []} + end + + characters = + characters + |> Enum.map(fn c -> + WandererApp.Maps.map_character( + c, + character_settings |> Enum.find(&(&1.character_id == c.id)) + ) + end) + + {:noreply, + socket + |> assign(user_characters: user_character_eve_ids) + |> assign(has_tracked_characters?: has_tracked_characters?(user_character_eve_ids)) + |> assign(character_settings: character_settings) + |> assign_async(:characters, fn -> + {:ok, %{characters: characters}} + end) + |> MapEventHandler.push_map_event( + "init", + %{ + user_characters: user_character_eve_ids, + reset: false + } + )} + end + + def handle_ui_event("hide_tracking", _, socket), + do: {:noreply, socket |> assign(show_tracking?: false)} + + def handle_ui_event(event, body, socket), + do: MapCoreEventHandler.handle_ui_event(event, body, socket) + + def has_tracked_characters?([]), do: false + def has_tracked_characters?(_user_characters), do: true + + def get_tracked_map_characters(map_id, current_user) do + case WandererApp.Api.MapCharacterSettings.tracked_by_map(%{ + map_id: map_id, + character_ids: current_user.characters |> Enum.map(& &1.id) + }) do + {:ok, settings} -> + {:ok, + settings + |> Enum.map(fn s -> s |> Ash.load!(:character) |> Map.get(:character) end)} + + _ -> + {:ok, []} + end + end + + def map_ui_character(character), + do: + character + |> Map.take([ + :eve_id, + :name, + :online, + :corporation_id, + :corporation_name, + :corporation_ticker, + :alliance_id, + :alliance_name, + :alliance_ticker + ]) + |> Map.put_new(:ship, WandererApp.Character.get_ship(character)) + |> Map.put_new(:location, get_location(character)) + + def add_characters([], _map_id, _track_character), do: :ok + + def add_characters([character | characters], map_id, track_character) do + map_id + |> WandererApp.Map.Server.add_character(character, track_character) + + add_characters(characters, map_id, track_character) + end + + def remove_characters([], _map_id), do: :ok + + def remove_characters([character | characters], map_id) do + map_id + |> WandererApp.Map.Server.remove_character(character.id) + + remove_characters(characters, map_id) + end + + def untrack_characters(characters, map_id) do + characters + |> Enum.each(fn character -> + WandererAppWeb.Presence.untrack(self(), map_id, character.id) + + WandererApp.Cache.put( + "#{inspect(self())}_map_#{map_id}:character_#{character.id}:tracked", + false + ) + + :ok = + Phoenix.PubSub.unsubscribe( + WandererApp.PubSub, + "character:#{character.eve_id}" + ) + end) + end + + def track_characters(_, _, false), do: :ok + + def track_characters([], _map_id, _is_track_character?), do: :ok + + def track_characters( + [character | characters], + map_id, + true + ) do + track_character(character, map_id) + + track_characters(characters, map_id, true) + end + + def track_character( + %{ + id: character_id, + eve_id: eve_id, + corporation_id: corporation_id, + alliance_id: alliance_id + }, + map_id + ) do + WandererAppWeb.Presence.track(self(), map_id, character_id, %{}) + + case WandererApp.Cache.lookup!( + "#{inspect(self())}_map_#{map_id}:character_#{character_id}:tracked", + false + ) do + true -> + :ok + + _ -> + :ok = + Phoenix.PubSub.subscribe( + WandererApp.PubSub, + "character:#{eve_id}" + ) + + :ok = + WandererApp.Cache.put( + "#{inspect(self())}_map_#{map_id}:character_#{character_id}:tracked", + true + ) + end + + case WandererApp.Cache.lookup( + "#{inspect(self())}_map_#{map_id}:corporation_#{corporation_id}:tracked", + false + ) do + {:ok, true} -> + :ok + + {:ok, false} -> + :ok = + Phoenix.PubSub.subscribe( + WandererApp.PubSub, + "corporation:#{corporation_id}" + ) + + :ok = + WandererApp.Cache.put( + "#{inspect(self())}_map_#{map_id}:corporation_#{corporation_id}:tracked", + true + ) + end + + case WandererApp.Cache.lookup( + "#{inspect(self())}_map_#{map_id}:alliance_#{alliance_id}:tracked", + false + ) do + {:ok, true} -> + :ok + + {:ok, false} -> + :ok = + Phoenix.PubSub.subscribe( + WandererApp.PubSub, + "alliance:#{alliance_id}" + ) + + :ok = + WandererApp.Cache.put( + "#{inspect(self())}_map_#{map_id}:alliance_#{alliance_id}:tracked", + true + ) + end + + :ok = WandererApp.Character.TrackerManager.start_tracking(character_id) + end + + defp get_location(character), + do: %{solar_system_id: character.solar_system_id, structure_id: character.structure_id} +end diff --git a/lib/wanderer_app_web/live/maps/event_handlers/map_connections_event_handler.ex b/lib/wanderer_app_web/live/maps/event_handlers/map_connections_event_handler.ex new file mode 100644 index 00000000..864860c6 --- /dev/null +++ b/lib/wanderer_app_web/live/maps/event_handlers/map_connections_event_handler.ex @@ -0,0 +1,197 @@ +defmodule WandererAppWeb.MapConnectionsEventHandler do + use WandererAppWeb, :live_component + use Phoenix.Component + require Logger + + alias WandererAppWeb.{MapEventHandler, MapCoreEventHandler} + + def handle_server_event(%{event: :update_connection, payload: connection}, socket), + do: + socket + |> MapEventHandler.push_map_event( + "update_connection", + MapEventHandler.map_ui_connection(connection) + ) + + def handle_server_event(%{event: :remove_connections, payload: connections}, socket) do + connection_ids = + connections |> Enum.map(&MapEventHandler.map_ui_connection/1) |> Enum.map(& &1.id) + + socket + |> MapEventHandler.push_map_event( + "remove_connections", + connection_ids + ) + end + + def handle_server_event(%{event: :add_connection, payload: connection}, socket) do + connections = [MapEventHandler.map_ui_connection(connection)] + + socket + |> MapEventHandler.push_map_event( + "add_connections", + connections + ) + end + + def handle_server_event(event, socket), + do: MapCoreEventHandler.handle_server_event(event, socket) + + def handle_ui_event( + "manual_add_connection", + %{"source" => solar_system_source_id, "target" => solar_system_target_id} = _event, + %{ + assigns: %{ + map_id: map_id, + current_user: current_user, + tracked_character_ids: tracked_character_ids, + has_tracked_characters?: true, + user_permissions: %{add_connection: true} + } + } = + socket + ) do + map_id + |> WandererApp.Map.Server.add_connection(%{ + solar_system_source_id: solar_system_source_id |> String.to_integer(), + solar_system_target_id: solar_system_target_id |> String.to_integer() + }) + + {:ok, _} = + WandererApp.User.ActivityTracker.track_map_event(:map_connection_added, %{ + character_id: tracked_character_ids |> List.first(), + user_id: current_user.id, + map_id: map_id, + solar_system_source_id: "#{solar_system_source_id}" |> String.to_integer(), + solar_system_target_id: "#{solar_system_target_id}" |> String.to_integer() + }) + + {:noreply, socket} + end + + def handle_ui_event( + "manual_delete_connection", + %{"source" => solar_system_source_id, "target" => solar_system_target_id} = _event, + %{ + assigns: %{ + map_id: map_id, + current_user: current_user, + tracked_character_ids: tracked_character_ids, + has_tracked_characters?: true, + user_permissions: %{delete_connection: true} + } + } = + socket + ) do + map_id + |> WandererApp.Map.Server.delete_connection(%{ + solar_system_source_id: solar_system_source_id |> String.to_integer(), + solar_system_target_id: solar_system_target_id |> String.to_integer() + }) + + {:ok, _} = + WandererApp.User.ActivityTracker.track_map_event(:map_connection_removed, %{ + character_id: tracked_character_ids |> List.first(), + user_id: current_user.id, + map_id: map_id, + solar_system_source_id: "#{solar_system_source_id}" |> String.to_integer(), + solar_system_target_id: "#{solar_system_target_id}" |> String.to_integer() + }) + + {:noreply, socket} + end + + def handle_ui_event( + "update_connection_" <> param, + %{ + "source" => solar_system_source_id, + "target" => solar_system_target_id, + "value" => value + } = _event, + %{ + assigns: %{ + map_id: map_id, + current_user: current_user, + tracked_character_ids: tracked_character_ids, + has_tracked_characters?: true, + user_permissions: %{update_system: true} + } + } = + socket + ) do + method_atom = + case param do + "time_status" -> :update_connection_time_status + "mass_status" -> :update_connection_mass_status + "ship_size_type" -> :update_connection_ship_size_type + "locked" -> :update_connection_locked + "custom_info" -> :update_connection_custom_info + _ -> nil + end + + key_atom = + case param do + "time_status" -> :time_status + "mass_status" -> :mass_status + "ship_size_type" -> :ship_size_type + "locked" -> :locked + "custom_info" -> :custom_info + _ -> nil + end + + {:ok, _} = + WandererApp.User.ActivityTracker.track_map_event(:map_connection_updated, %{ + character_id: tracked_character_ids |> List.first(), + user_id: current_user.id, + map_id: map_id, + solar_system_source_id: "#{solar_system_source_id}" |> String.to_integer(), + solar_system_target_id: "#{solar_system_target_id}" |> String.to_integer(), + key: key_atom, + value: value + }) + + apply(WandererApp.Map.Server, method_atom, [ + map_id, + %{ + solar_system_source_id: "#{solar_system_source_id}" |> String.to_integer(), + solar_system_target_id: "#{solar_system_target_id}" |> String.to_integer() + } + |> Map.put_new(key_atom, value) + ]) + + {:noreply, socket} + end + + def handle_ui_event( + "get_passages", + %{"from" => from, "to" => to} = _event, + %{assigns: %{map_id: map_id}} = socket + ) do + {:ok, passages} = map_id |> get_connection_passages(from, to) + + {:reply, passages, socket} + end + + def handle_ui_event(event, body, socket), + do: MapCoreEventHandler.handle_ui_event(event, body, socket) + + defp get_connection_passages(map_id, from, to) do + {:ok, passages} = WandererApp.MapChainPassagesRepo.by_connection(map_id, from, to) + + passages = + passages + |> Enum.map(fn p -> + %{ + p + | character: p.character |> MapEventHandler.map_ui_character_stat() + } + |> Map.put_new( + :ship, + WandererApp.Character.get_ship(%{ship: p.ship_type_id, ship_name: p.ship_name}) + ) + |> Map.drop([:ship_type_id, :ship_name]) + end) + + {:ok, %{passages: passages}} + end +end diff --git a/lib/wanderer_app_web/live/maps/event_handlers/map_core_event_handler.ex b/lib/wanderer_app_web/live/maps/event_handlers/map_core_event_handler.ex new file mode 100644 index 00000000..3c9e2815 --- /dev/null +++ b/lib/wanderer_app_web/live/maps/event_handlers/map_core_event_handler.ex @@ -0,0 +1,544 @@ +defmodule WandererAppWeb.MapCoreEventHandler do + use WandererAppWeb, :live_component + use Phoenix.Component + require Logger + + alias WandererAppWeb.{MapEventHandler, MapCharactersEventHandler} + + def handle_server_event(:update_permissions, socket) do + DebounceAndThrottle.Debounce.apply( + Process, + :send_after, + [self(), :refresh_permissions, 100], + "update_permissions_#{inspect(self())}", + 1000 + ) + + socket + end + + def handle_server_event( + :refresh_permissions, + %{assigns: %{current_user: current_user, map_slug: map_slug}} = socket + ) do + {:ok, %{id: map_id, user_permissions: user_permissions, owner_id: owner_id}} = + map_slug + |> WandererApp.Api.Map.get_map_by_slug!() + |> Ash.load(:user_permissions, actor: current_user) + + user_permissions = + WandererApp.Permissions.get_map_permissions( + user_permissions, + owner_id, + current_user.characters |> Enum.map(& &1.id) + ) + + case user_permissions do + %{view_system: false} -> + socket + |> Phoenix.LiveView.put_flash(:error, "Your access to the map have been revoked.") + |> Phoenix.LiveView.push_navigate(to: ~p"/maps") + + %{track_character: track_character} -> + {:ok, map_characters} = + case WandererApp.Api.MapCharacterSettings.tracked_by_map(%{ + map_id: map_id, + character_ids: current_user.characters |> Enum.map(& &1.id) + }) do + {:ok, settings} -> + {:ok, + settings + |> Enum.map(fn s -> s |> Ash.load!(:character) |> Map.get(:character) end)} + + _ -> + {:ok, []} + end + + case track_character do + false -> + :ok = MapCharactersEventHandler.untrack_characters(map_characters, map_id) + :ok = MapCharactersEventHandler.remove_characters(map_characters, map_id) + + _ -> + :ok = MapCharactersEventHandler.track_characters(map_characters, map_id, true) + + :ok = + MapCharactersEventHandler.add_characters(map_characters, map_id, track_character) + end + + socket + |> assign(user_permissions: user_permissions) + |> MapEventHandler.push_map_event( + "user_permissions", + user_permissions + ) + end + end + + def handle_server_event( + %{ + event: :load_map + }, + %{assigns: %{current_user: current_user, map_slug: map_slug}} = socket + ) do + ErrorTracker.set_context(%{user_id: current_user.id}) + + map_slug + |> WandererApp.MapRepo.get_by_slug_with_permissions(current_user) + |> case do + {:ok, map} -> + socket |> init_map(map) + + {:error, _} -> + socket + |> put_flash( + :error, + "Something went wrong. Please try one more time or submit an issue." + ) + |> push_navigate(to: ~p"/maps") + end + end + + def handle_server_event( + %{event: :map_server_started}, + socket + ), + do: socket |> handle_map_server_started() + + def handle_server_event(%{event: :update_map, payload: map_diff}, socket), + do: + socket + |> MapEventHandler.push_map_event( + "map_updated", + map_diff + ) + + def handle_server_event( + %{event: "presence_diff"}, + socket + ), + do: socket + + def handle_server_event(event, socket) do + Logger.warning(fn -> "unhandled map core event: #{inspect(event)}" end) + socket + end + + def handle_ui_event("ui_loaded", _body, %{assigns: %{map_slug: map_slug} = assigns} = socket) do + assigns + |> Map.get(:map_id) + |> case do + map_id when not is_nil(map_id) -> + maybe_start_map(map_id) + + _ -> + WandererApp.Cache.insert("map_#{map_slug}:ui_loaded", true) + end + + {:noreply, socket} + end + + def handle_ui_event( + "live_select_change", + %{"id" => id, "text" => text}, + socket + ) + when id == "_system_id_live_select_component" do + options = + WandererApp.Api.MapSolarSystem.find_by_name!(%{name: text}) + |> Enum.take(100) + |> Enum.map(&map_system/1) + + send_update(LiveSelect.Component, options: options, id: id) + + {:noreply, socket} + end + + def handle_ui_event("toggle_track_" <> character_id, _, socket), + do: + MapCharactersEventHandler.handle_ui_event( + "toggle_track", + %{"character-id" => character_id}, + socket + ) + + def handle_ui_event( + "get_user_settings", + _, + %{assigns: %{map_id: map_id, current_user: current_user}} = socket + ) do + {:ok, user_settings} = + WandererApp.MapUserSettingsRepo.get!(map_id, current_user.id) + |> WandererApp.MapUserSettingsRepo.to_form_data() + + {:reply, %{user_settings: user_settings}, socket} + end + + def handle_ui_event( + "update_user_settings", + user_settings_form, + %{assigns: %{map_id: map_id, current_user: current_user}} = socket + ) do + settings = + user_settings_form + |> Map.take(["select_on_spash", "link_signature_on_splash", "delete_connection_with_sigs"]) + |> Jason.encode!() + + {:ok, user_settings} = + WandererApp.MapUserSettingsRepo.create_or_update(map_id, current_user.id, settings) + + {:noreply, + socket |> assign(user_settings_form: user_settings_form, map_user_settings: user_settings)} + end + + def handle_ui_event( + "log_map_error", + %{"componentStack" => component_stack, "error" => error}, + socket + ) do + Logger.error(fn -> "map_ui_error: #{error} \n#{component_stack} " end) + + {:noreply, + socket + |> put_flash(:error, "Something went wrong. Please try refresh page or submit an issue.") + |> push_event("js-exec", %{ + to: "#map-loader", + attr: "data-loading", + timeout: 100 + })} + end + + def handle_ui_event("noop", _, socket), do: {:noreply, socket} + + def handle_ui_event( + _event, + _body, + %{assigns: %{has_tracked_characters?: false}} = + socket + ), + do: + {:noreply, + socket + |> put_flash( + :error, + "You should enable tracking for at least one character." + )} + + def handle_ui_event(event, body, socket) do + Logger.warning(fn -> "unhandled map ui event: #{event} #{inspect(body)}" end) + {:noreply, socket} + end + + defp maybe_start_map(map_id) do + {:ok, map_server_started} = WandererApp.Cache.lookup("map_#{map_id}:started", false) + + if map_server_started do + Process.send_after(self(), %{event: :map_server_started}, 10) + else + WandererApp.Map.Manager.start_map(map_id) + end + end + + defp init_map( + %{assigns: %{current_user: current_user, map_slug: map_slug}} = socket, + %{ + id: map_id, + deleted: false, + only_tracked_characters: only_tracked_characters, + user_permissions: user_permissions, + name: map_name, + owner_id: owner_id + } = map + ) do + user_permissions = + WandererApp.Permissions.get_map_permissions( + user_permissions, + owner_id, + current_user.characters |> Enum.map(& &1.id) + ) + + {:ok, character_settings} = + case WandererApp.Api.MapCharacterSettings.read_by_map(%{map_id: map_id}) do + {:ok, settings} -> {:ok, settings} + _ -> {:ok, []} + end + + {:ok, %{characters: availaible_map_characters}} = + WandererApp.Maps.load_characters(map, character_settings, current_user.id) + + can_view? = user_permissions.view_system + can_track? = user_permissions.track_character + + tracked_character_ids = + availaible_map_characters |> Enum.filter(& &1.tracked) |> Enum.map(& &1.id) + + all_character_tracked? = + not (availaible_map_characters |> Enum.empty?()) and + availaible_map_characters |> Enum.all?(& &1.tracked) + + cond do + (only_tracked_characters and can_track? and all_character_tracked?) or + (not only_tracked_characters and can_view?) -> + Phoenix.PubSub.subscribe(WandererApp.PubSub, map_id) + {:ok, ui_loaded} = WandererApp.Cache.get_and_remove("map_#{map_slug}:ui_loaded", false) + + if ui_loaded do + maybe_start_map(map_id) + end + + socket + |> assign( + map_id: map_id, + page_title: map_name, + user_permissions: user_permissions, + tracked_character_ids: tracked_character_ids, + only_tracked_characters: only_tracked_characters + ) + + only_tracked_characters and can_track? and not all_character_tracked? -> + Process.send_after(self(), :not_all_characters_tracked, 10) + socket + + true -> + Process.send_after(self(), :no_permissions, 10) + socket + end + end + + defp init_map(socket, _map) do + Process.send_after(self(), :no_access, 10) + socket + end + + defp handle_map_server_started( + %{ + assigns: %{ + current_user: current_user, + map_id: map_id, + user_permissions: + %{view_system: true, track_character: track_character} = user_permissions + } + } = socket + ) do + with {:ok, _} <- current_user |> WandererApp.Api.User.update_last_map(%{last_map_id: map_id}), + {:ok, map_user_settings} <- WandererApp.MapUserSettingsRepo.get(map_id, current_user.id), + {:ok, tracked_map_characters} <- + MapCharactersEventHandler.get_tracked_map_characters(map_id, current_user), + {:ok, characters_limit} <- map_id |> WandererApp.Map.get_characters_limit(), + {:ok, present_character_ids} <- + WandererApp.Cache.lookup("map_#{map_id}:presence_character_ids", []), + {:ok, kills} <- WandererApp.Cache.lookup("map_#{map_id}:zkb_kills", Map.new()) do + user_character_eve_ids = tracked_map_characters |> Enum.map(& &1.eve_id) + + events = + case tracked_map_characters |> Enum.any?(&(&1.access_token == nil)) do + true -> + [:invalid_token_message] + + _ -> + [] + end + + events = + case tracked_map_characters |> Enum.empty?() do + true -> + events ++ [:empty_tracked_characters] + + _ -> + events + end + + events = + case present_character_ids |> Enum.count() < characters_limit do + true -> + events ++ [{:track_characters, tracked_map_characters, track_character}] + + _ -> + events ++ [:map_character_limit] + end + + initial_data = + map_id + |> get_map_data() + |> Map.merge(%{ + kills: + kills + |> Enum.filter(fn {_, kills} -> kills > 0 end) + |> Enum.map(&MapEventHandler.map_ui_kill/1), + present_characters: + present_character_ids + |> WandererApp.Character.get_character_eve_ids!(), + user_characters: user_character_eve_ids, + user_permissions: user_permissions, + system_static_infos: nil, + wormhole_types: nil, + effects: nil, + reset: false + }) + + system_static_infos = + map_id + |> WandererApp.Map.list_systems!() + |> Enum.map(&WandererApp.CachedInfo.get_system_static_info!(&1.solar_system_id)) + |> Enum.map(&MapEventHandler.map_ui_system_static_info/1) + + initial_data = + initial_data + |> Map.put( + :wormholes, + WandererApp.CachedInfo.get_wormhole_types!() + ) + |> Map.put( + :effects, + WandererApp.CachedInfo.get_effects!() + ) + |> Map.put( + :system_static_infos, + system_static_infos + ) + |> Map.put(:reset, true) + + socket + |> map_start(%{ + map_id: map_id, + map_user_settings: map_user_settings, + user_characters: user_character_eve_ids, + initial_data: initial_data, + events: events + }) + else + error -> + Logger.error(fn -> "map_start_error: #{error}" end) + Process.send_after(self(), :no_access, 10) + + socket + end + end + + defp handle_map_server_started(socket) do + Process.send_after(self(), :no_access, 10) + socket + end + + defp map_start( + socket, + %{ + map_id: map_id, + map_user_settings: map_user_settings, + user_characters: user_character_eve_ids, + initial_data: initial_data, + events: events + } = _started_data + ) do + socket = + socket + |> handle_map_start_events(map_id, events) + + map_characters = map_id |> WandererApp.Map.list_characters() + + socket + |> assign( + map_loaded?: true, + map_user_settings: map_user_settings, + user_characters: user_character_eve_ids, + has_tracked_characters?: + MapCharactersEventHandler.has_tracked_characters?(user_character_eve_ids) + ) + |> MapEventHandler.push_map_event( + "init", + initial_data + |> Map.put( + :characters, + map_characters |> Enum.map(&MapCharactersEventHandler.map_ui_character/1) + ) + ) + |> push_event("js-exec", %{ + to: "#map-loader", + attr: "data-loaded" + }) + end + + defp handle_map_start_events(socket, map_id, events) do + events + |> Enum.reduce(socket, fn event, socket -> + case event do + {:track_characters, map_characters, track_character} -> + :ok = + MapCharactersEventHandler.track_characters(map_characters, map_id, track_character) + + :ok = MapCharactersEventHandler.add_characters(map_characters, map_id, track_character) + socket + + :invalid_token_message -> + socket + |> put_flash( + :error, + "One of your characters has expired token. Please refresh it on characters page." + ) + + :empty_tracked_characters -> + socket + |> put_flash( + :info, + "You should enable tracking for at least one character to work with map." + ) + + :map_character_limit -> + socket + |> put_flash( + :error, + "Map reached its character limit, your characters won't be tracked. Please contact administrator." + ) + + _ -> + socket + end + end) + end + + defp get_map_data(map_id, include_static_data? \\ true) do + {:ok, hubs} = map_id |> WandererApp.Map.list_hubs() + {:ok, connections} = map_id |> WandererApp.Map.list_connections() + {:ok, systems} = map_id |> WandererApp.Map.list_systems() + + %{ + systems: + systems + |> Enum.map(fn system -> MapEventHandler.map_ui_system(system, include_static_data?) end), + hubs: hubs, + connections: connections |> Enum.map(&MapEventHandler.map_ui_connection/1) + } + end + + defp get_tracked_map_characters(map_id, current_user) do + case WandererApp.Api.MapCharacterSettings.tracked_by_map(%{ + map_id: map_id, + character_ids: current_user.characters |> Enum.map(& &1.id) + }) do + {:ok, settings} -> + {:ok, + settings + |> Enum.map(fn s -> s |> Ash.load!(:character) |> Map.get(:character) end)} + + _ -> + {:ok, []} + end + end + + defp map_system( + %{ + solar_system_name: solar_system_name, + constellation_name: constellation_name, + region_name: region_name, + solar_system_id: solar_system_id, + class_title: class_title + } = _system + ), + do: %{ + label: solar_system_name, + value: solar_system_id, + constellation_name: constellation_name, + region_name: region_name, + class_title: class_title + } +end diff --git a/lib/wanderer_app_web/live/maps/event_handlers/map_routes_event_handler.ex b/lib/wanderer_app_web/live/maps/event_handlers/map_routes_event_handler.ex new file mode 100644 index 00000000..17ed30d9 --- /dev/null +++ b/lib/wanderer_app_web/live/maps/event_handlers/map_routes_event_handler.ex @@ -0,0 +1,131 @@ +defmodule WandererAppWeb.MapRoutesEventHandler do + use WandererAppWeb, :live_component + use Phoenix.Component + require Logger + + alias WandererAppWeb.{MapEventHandler, MapCoreEventHandler} + + def handle_server_event( + %{ + event: :routes, + payload: {solar_system_id, %{routes: routes, systems_static_data: systems_static_data}} + }, + socket + ), + do: + socket + |> MapEventHandler.push_map_event( + "routes", + %{ + solar_system_id: solar_system_id, + loading: false, + routes: routes, + systems_static_data: systems_static_data + } + ) + + def handle_server_event(event, socket), + do: MapCoreEventHandler.handle_server_event(event, socket) + + def handle_ui_event( + "get_routes", + %{"system_id" => solar_system_id, "routes_settings" => routes_settings} = _event, + %{assigns: %{map_id: map_id, map_loaded?: true}} = socket + ) do + Task.async(fn -> + {:ok, hubs} = map_id |> WandererApp.Map.list_hubs() + + {:ok, routes} = + WandererApp.Maps.find_routes( + map_id, + hubs, + solar_system_id, + get_routes_settings(routes_settings) + ) + + {:routes, {solar_system_id, routes}} + end) + + {:noreply, socket} + end + + def handle_ui_event( + "set_autopilot_waypoint", + %{ + "character_eve_ids" => character_eve_ids, + "add_to_beginning" => add_to_beginning, + "clear_other_waypoints" => clear_other_waypoints, + "destination_id" => destination_id + } = _event, + %{assigns: %{current_user: current_user, has_tracked_characters?: true}} = socket + ) do + character_eve_ids + |> Task.async_stream(fn character_eve_id -> + set_autopilot_waypoint( + current_user, + character_eve_id, + add_to_beginning, + clear_other_waypoints, + destination_id + ) + end) + |> Enum.map(fn _result -> :skip end) + + {:noreply, socket} + end + + def handle_ui_event(event, body, socket), + do: MapCoreEventHandler.handle_ui_event(event, body, socket) + + defp get_routes_settings(%{ + "path_type" => path_type, + "include_mass_crit" => include_mass_crit, + "include_eol" => include_eol, + "include_frig" => include_frig, + "include_cruise" => include_cruise, + "avoid_wormholes" => avoid_wormholes, + "avoid_pochven" => avoid_pochven, + "avoid_edencom" => avoid_edencom, + "avoid_triglavian" => avoid_triglavian, + "include_thera" => include_thera, + "avoid" => avoid + }), + do: %{ + path_type: path_type, + include_mass_crit: include_mass_crit, + include_eol: include_eol, + include_frig: include_frig, + include_cruise: include_cruise, + avoid_wormholes: avoid_wormholes, + avoid_pochven: avoid_pochven, + avoid_edencom: avoid_edencom, + avoid_triglavian: avoid_triglavian, + include_thera: include_thera, + avoid: avoid + } + + defp get_routes_settings(_), do: %{} + + defp set_autopilot_waypoint( + current_user, + character_eve_id, + add_to_beginning, + clear_other_waypoints, + destination_id + ) do + case current_user.characters + |> Enum.find(fn c -> c.eve_id == character_eve_id end) do + nil -> + :skip + + %{id: character_id} = _character -> + character_id + |> WandererApp.Character.set_autopilot_waypoint(destination_id, + add_to_beginning: add_to_beginning, + clear_other_waypoints: clear_other_waypoints + ) + + :skip + end + end +end diff --git a/lib/wanderer_app_web/live/maps/event_handlers/map_signatures_event_handler.ex b/lib/wanderer_app_web/live/maps/event_handlers/map_signatures_event_handler.ex new file mode 100644 index 00000000..6ad7a8fe --- /dev/null +++ b/lib/wanderer_app_web/live/maps/event_handlers/map_signatures_event_handler.ex @@ -0,0 +1,350 @@ +defmodule WandererAppWeb.MapSignaturesEventHandler do + use WandererAppWeb, :live_component + use Phoenix.Component + require Logger + + alias WandererAppWeb.{MapEventHandler, MapCoreEventHandler} + + def handle_server_event( + %{ + event: :maybe_link_signature, + payload: %{ + character_id: character_id, + solar_system_source: solar_system_source, + solar_system_target: solar_system_target + } + }, + %{ + assigns: %{ + current_user: current_user, + map_id: map_id, + map_user_settings: map_user_settings + } + } = socket + ) do + is_user_character = + current_user.characters |> Enum.map(& &1.id) |> Enum.member?(character_id) + + is_link_signature_on_splash = + map_user_settings + |> WandererApp.MapUserSettingsRepo.to_form_data!() + |> WandererApp.MapUserSettingsRepo.get_boolean_setting("link_signature_on_splash") + + {:ok, signatures} = + WandererApp.Api.MapSystem.read_by_map_and_solar_system(%{ + map_id: map_id, + solar_system_id: solar_system_source + }) + |> case do + {:ok, system} -> + {:ok, get_system_signatures(system.id)} + + _ -> + {:ok, []} + end + + (is_user_character && is_link_signature_on_splash && not (signatures |> Enum.empty?())) + |> case do + true -> + socket + |> MapEventHandler.push_map_event("link_signature_to_system", %{ + solar_system_source: solar_system_source, + solar_system_target: solar_system_target + }) + + false -> + socket + end + end + + def handle_server_event( + %{event: :signatures_updated, payload: solar_system_id}, + socket + ), + do: + socket + |> MapEventHandler.push_map_event( + "signatures_updated", + solar_system_id + ) + + def handle_server_event(event, socket), + do: MapCoreEventHandler.handle_server_event(event, socket) + + def handle_ui_event( + "update_signatures", + %{ + "system_id" => solar_system_id, + "added" => added_signatures, + "updated" => updated_signatures, + "removed" => removed_signatures + }, + %{ + assigns: %{ + map_id: map_id, + map_user_settings: map_user_settings, + user_characters: user_characters, + user_permissions: %{update_system: true} + } + } = socket + ) do + WandererApp.Api.MapSystem.read_by_map_and_solar_system(%{ + map_id: map_id, + solar_system_id: solar_system_id |> String.to_integer() + }) + |> case do + {:ok, system} -> + first_character_eve_id = + user_characters |> List.first() + + case not is_nil(first_character_eve_id) do + true -> + added_signatures = + added_signatures + |> parse_signatures(first_character_eve_id, system.id) + + updated_signatures = + updated_signatures + |> parse_signatures(first_character_eve_id, system.id) + + updated_signatures_eve_ids = + updated_signatures + |> Enum.map(fn s -> s.eve_id end) + + removed_signatures_eve_ids = + removed_signatures + |> parse_signatures(first_character_eve_id, system.id) + |> Enum.map(fn s -> s.eve_id end) + + delete_connection_with_sigs = + map_user_settings + |> WandererApp.MapUserSettingsRepo.to_form_data!() + |> WandererApp.MapUserSettingsRepo.get_boolean_setting( + "delete_connection_with_sigs" + ) + + WandererApp.Api.MapSystemSignature.by_system_id!(system.id) + |> Enum.filter(fn s -> s.eve_id in removed_signatures_eve_ids end) + |> Enum.each(fn s -> + if delete_connection_with_sigs && not is_nil(s.linked_system_id) do + map_id + |> WandererApp.Map.Server.delete_connection(%{ + solar_system_source_id: solar_system_id |> String.to_integer(), + solar_system_target_id: s.linked_system_id + }) + end + + s + |> Ash.destroy!() + end) + + WandererApp.Api.MapSystemSignature.by_system_id!(system.id) + |> Enum.filter(fn s -> s.eve_id in updated_signatures_eve_ids end) + |> Enum.each(fn s -> + updated = updated_signatures |> Enum.find(fn u -> u.eve_id == s.eve_id end) + + if not is_nil(updated) do + s + |> WandererApp.Api.MapSystemSignature.update(updated) + end + end) + + added_signatures + |> Enum.map(fn s -> + s |> WandererApp.Api.MapSystemSignature.create!() + end) + + Phoenix.PubSub.broadcast!(WandererApp.PubSub, map_id, %{ + event: :signatures_updated, + payload: system.solar_system_id + }) + + {:reply, %{signatures: get_system_signatures(system.id)}, socket} + + _ -> + {:reply, %{signatures: []}, + socket + |> put_flash( + :error, + "You should enable tracking for at least one character to work with signatures." + )} + end + + _ -> + {:noreply, socket} + end + end + + def handle_ui_event( + "get_signatures", + %{"system_id" => solar_system_id}, + %{ + assigns: %{ + map_id: map_id + } + } = socket + ) do + case WandererApp.Api.MapSystem.read_by_map_and_solar_system(%{ + map_id: map_id, + solar_system_id: solar_system_id |> String.to_integer() + }) do + {:ok, system} -> + {:reply, %{signatures: get_system_signatures(system.id)}, socket} + + _ -> + {:reply, %{signatures: []}, socket} + end + end + + def handle_ui_event( + "link_signature_to_system", + %{ + "signature_eve_id" => signature_eve_id, + "solar_system_source" => solar_system_source, + "solar_system_target" => solar_system_target + }, + %{ + assigns: %{ + map_id: map_id, + user_characters: user_characters, + user_permissions: %{update_system: true} + } + } = socket + ) do + case WandererApp.Api.MapSystem.read_by_map_and_solar_system(%{ + map_id: map_id, + solar_system_id: solar_system_source + }) do + {:ok, system} -> + first_character_eve_id = + user_characters |> List.first() + + case not is_nil(first_character_eve_id) do + true -> + WandererApp.Api.MapSystemSignature.by_system_id!(system.id) + |> Enum.filter(fn s -> s.eve_id == signature_eve_id end) + |> Enum.each(fn s -> + s + |> WandererApp.Api.MapSystemSignature.update_linked_system(%{ + linked_system_id: solar_system_target + }) + end) + + Phoenix.PubSub.broadcast!(WandererApp.PubSub, map_id, %{ + event: :signatures_updated, + payload: solar_system_source + }) + + {:noreply, socket} + + _ -> + {:noreply, + socket + |> put_flash( + :error, + "You should enable tracking for at least one character to work with signatures." + )} + end + + _ -> + {:noreply, socket} + end + end + + def handle_ui_event( + "unlink_signature", + %{ + "signature_eve_id" => signature_eve_id, + "solar_system_source" => solar_system_source + }, + %{ + assigns: %{ + map_id: map_id, + user_characters: user_characters, + user_permissions: %{update_system: true} + } + } = socket + ) do + case WandererApp.Api.MapSystem.read_by_map_and_solar_system(%{ + map_id: map_id, + solar_system_id: solar_system_source + }) do + {:ok, system} -> + first_character_eve_id = + user_characters |> List.first() + + case not is_nil(first_character_eve_id) do + true -> + WandererApp.Api.MapSystemSignature.by_system_id!(system.id) + |> Enum.filter(fn s -> s.eve_id == signature_eve_id end) + |> Enum.each(fn s -> + s + |> WandererApp.Api.MapSystemSignature.update_linked_system(%{ + linked_system_id: nil + }) + end) + + Phoenix.PubSub.broadcast!(WandererApp.PubSub, map_id, %{ + event: :signatures_updated, + payload: solar_system_source + }) + + {:noreply, socket} + + _ -> + {:noreply, + socket + |> put_flash( + :error, + "You should enable tracking for at least one character to work with signatures." + )} + end + + _ -> + {:noreply, socket} + end + end + + def handle_ui_event(event, body, socket), + do: MapCoreEventHandler.handle_ui_event(event, body, socket) + + defp get_system_signatures(system_id), + do: + system_id + |> WandererApp.Api.MapSystemSignature.by_system_id!() + |> Enum.map(fn %{updated_at: updated_at, linked_system_id: linked_system_id} = s -> + s + |> Map.take([ + :eve_id, + :name, + :description, + :kind, + :group, + :type, + :updated_at + ]) + |> Map.put(:linked_system, MapEventHandler.get_system_static_info(linked_system_id)) + |> Map.put(:updated_at, updated_at |> Calendar.strftime("%Y/%m/%d %H:%M:%S")) + end) + + defp parse_signatures(signatures, character_eve_id, system_id), + do: + signatures + |> Enum.map(fn %{ + "eve_id" => eve_id, + "name" => name, + "kind" => kind, + "group" => group + } = signature -> + %{ + system_id: system_id, + eve_id: eve_id, + name: name, + description: Map.get(signature, "description"), + kind: kind, + group: group, + type: Map.get(signature, "type"), + character_eve_id: character_eve_id + } + end) +end diff --git a/lib/wanderer_app_web/live/maps/event_handlers/map_systems_event_handler.ex b/lib/wanderer_app_web/live/maps/event_handlers/map_systems_event_handler.ex new file mode 100644 index 00000000..82b9c595 --- /dev/null +++ b/lib/wanderer_app_web/live/maps/event_handlers/map_systems_event_handler.ex @@ -0,0 +1,326 @@ +defmodule WandererAppWeb.MapSystemsEventHandler do + use WandererAppWeb, :live_component + use Phoenix.Component + require Logger + + alias WandererAppWeb.{MapEventHandler, MapCoreEventHandler} + + def handle_server_event(%{event: :add_system, payload: system}, socket), + do: + socket + |> MapEventHandler.push_map_event("add_systems", [MapEventHandler.map_ui_system(system)]) + + def handle_server_event(%{event: :update_system, payload: system}, socket), + do: + socket + |> MapEventHandler.push_map_event("update_systems", [MapEventHandler.map_ui_system(system)]) + + def handle_server_event(%{event: :systems_removed, payload: solar_system_ids}, socket), + do: + socket + |> MapEventHandler.push_map_event("remove_systems", solar_system_ids) + + def handle_server_event( + %{ + event: :maybe_select_system, + payload: %{ + character_id: character_id, + solar_system_id: solar_system_id + } + }, + %{assigns: %{current_user: current_user, map_user_settings: map_user_settings}} = socket + ) do + is_user_character = + current_user.characters |> Enum.map(& &1.id) |> Enum.member?(character_id) + + is_select_on_spash = + map_user_settings + |> WandererApp.MapUserSettingsRepo.to_form_data!() + |> WandererApp.MapUserSettingsRepo.get_boolean_setting("select_on_spash") + + (is_user_character && is_select_on_spash) + |> case do + true -> + socket + |> MapEventHandler.push_map_event("select_system", solar_system_id) + + false -> + socket + end + end + + def handle_server_event(%{event: :kills_updated, payload: kills}, socket) do + kills = + kills + |> Enum.map(&MapEventHandler.map_ui_kill/1) + + socket + |> MapEventHandler.push_map_event( + "kills_updated", + kills + ) + end + + def handle_server_event(event, socket), + do: MapCoreEventHandler.handle_server_event(event, socket) + + def handle_ui_event( + "add_system", + %{"system_id" => solar_system_id} = _event, + %{ + assigns: + %{ + map_id: map_id, + map_slug: map_slug, + current_user: current_user, + tracked_character_ids: tracked_character_ids, + user_permissions: %{add_system: true} + } = assigns + } = socket + ) + when is_binary(solar_system_id) and solar_system_id != "" do + coordinates = Map.get(assigns, :coordinates) + + WandererApp.Map.Server.add_system( + map_id, + %{ + solar_system_id: solar_system_id |> String.to_integer(), + coordinates: coordinates + }, + current_user.id, + tracked_character_ids |> List.first() + ) + + {:noreply, + socket + |> push_patch(to: ~p"/#{map_slug}")} + end + + def handle_ui_event( + "manual_add_system", + %{"coordinates" => coordinates} = _event, + %{ + assigns: %{ + has_tracked_characters?: true, + map_slug: map_slug, + user_permissions: %{add_system: true} + } + } = + socket + ), + do: + {:noreply, + socket + |> assign(coordinates: coordinates) + |> push_patch(to: ~p"/#{map_slug}/add-system")} + + def handle_ui_event( + "add_hub", + %{"system_id" => solar_system_id} = _event, + %{ + assigns: %{ + map_id: map_id, + current_user: current_user, + tracked_character_ids: tracked_character_ids, + has_tracked_characters?: true, + user_permissions: %{update_system: true} + } + } = + socket + ) do + map_id + |> WandererApp.Map.Server.add_hub(%{ + solar_system_id: solar_system_id + }) + + {:ok, _} = + WandererApp.User.ActivityTracker.track_map_event(:hub_added, %{ + character_id: tracked_character_ids |> List.first(), + user_id: current_user.id, + map_id: map_id, + solar_system_id: solar_system_id + }) + + {:noreply, socket} + end + + def handle_ui_event( + "delete_hub", + %{"system_id" => solar_system_id} = _event, + %{ + assigns: %{ + map_id: map_id, + current_user: current_user, + tracked_character_ids: tracked_character_ids, + has_tracked_characters?: true, + user_permissions: %{update_system: true} + } + } = + socket + ) do + map_id + |> WandererApp.Map.Server.remove_hub(%{ + solar_system_id: solar_system_id + }) + + {:ok, _} = + WandererApp.User.ActivityTracker.track_map_event(:hub_removed, %{ + character_id: tracked_character_ids |> List.first(), + user_id: current_user.id, + map_id: map_id, + solar_system_id: solar_system_id + }) + + {:noreply, socket} + end + + def handle_ui_event( + "update_system_position", + position, + %{ + assigns: %{ + map_id: map_id, + has_tracked_characters?: true, + user_permissions: %{update_system: true} + } + } = socket + ) do + map_id + |> update_system_position(position) + + {:noreply, socket} + end + + def handle_ui_event( + "update_system_positions", + positions, + %{ + assigns: %{ + map_id: map_id, + has_tracked_characters?: true, + user_permissions: %{update_system: true} + } + } = socket + ) do + map_id + |> update_system_positions(positions) + + {:noreply, socket} + end + + def handle_ui_event( + "update_system_" <> param, + %{"system_id" => solar_system_id, "value" => value} = _event, + %{ + assigns: %{ + map_id: map_id, + current_user: current_user, + tracked_character_ids: tracked_character_ids, + has_tracked_characters?: true, + user_permissions: %{update_system: true} + } + } = + socket + ) do + method_atom = + case param do + "name" -> :update_system_name + "description" -> :update_system_description + "labels" -> :update_system_labels + "locked" -> :update_system_locked + "tag" -> :update_system_tag + "status" -> :update_system_status + _ -> nil + end + + key_atom = + case param do + "name" -> :name + "description" -> :description + "labels" -> :labels + "locked" -> :locked + "tag" -> :tag + "status" -> :status + _ -> :none + end + + apply(WandererApp.Map.Server, method_atom, [ + map_id, + %{ + solar_system_id: "#{solar_system_id}" |> String.to_integer() + } + |> Map.put_new(key_atom, value) + ]) + + {:ok, _} = + WandererApp.User.ActivityTracker.track_map_event(:system_updated, %{ + character_id: tracked_character_ids |> List.first(), + user_id: current_user.id, + map_id: map_id, + solar_system_id: "#{solar_system_id}" |> String.to_integer(), + key: key_atom, + value: value + }) + + {:noreply, socket} + end + + def handle_ui_event( + "get_system_static_infos", + %{"solar_system_ids" => solar_system_ids} = _event, + socket + ) do + system_static_infos = + solar_system_ids + |> Enum.map(&WandererApp.CachedInfo.get_system_static_info!/1) + |> Enum.map(&MapEventHandler.map_ui_system_static_info/1) + + {:reply, %{system_static_infos: system_static_infos}, socket} + end + + def handle_ui_event( + "delete_systems", + solar_system_ids, + %{ + assigns: %{ + map_id: map_id, + current_user: current_user, + tracked_character_ids: tracked_character_ids, + has_tracked_characters?: true, + user_permissions: %{delete_system: true} + } + } = + socket + ) do + map_id + |> WandererApp.Map.Server.delete_systems( + solar_system_ids |> Enum.map(&String.to_integer/1), + current_user.id, + tracked_character_ids |> List.first() + ) + + {:noreply, socket} + end + + def handle_ui_event(event, body, socket), + do: MapCoreEventHandler.handle_ui_event(event, body, socket) + + defp update_system_positions(_map_id, []), do: :ok + + defp update_system_positions(map_id, [position | rest]) do + update_system_position(map_id, position) + update_system_positions(map_id, rest) + end + + defp update_system_position(map_id, %{ + "position" => %{"x" => x, "y" => y}, + "solar_system_id" => solar_system_id + }), + do: + map_id + |> WandererApp.Map.Server.update_system_position(%{ + solar_system_id: solar_system_id |> String.to_integer(), + position_x: x, + position_y: y + }) +end diff --git a/lib/wanderer_app_web/live/maps/map_event_handler.ex b/lib/wanderer_app_web/live/maps/map_event_handler.ex new file mode 100644 index 00000000..de28206e --- /dev/null +++ b/lib/wanderer_app_web/live/maps/map_event_handler.ex @@ -0,0 +1,292 @@ +defmodule WandererAppWeb.MapEventHandler do + use WandererAppWeb, :live_component + use Phoenix.Component + require Logger + + alias WandererAppWeb.{ + MapActivityEventHandler, + MapCharactersEventHandler, + MapConnectionsEventHandler, + MapCoreEventHandler, + MapRoutesEventHandler, + MapSignaturesEventHandler, + MapSystemsEventHandler + } + + @map_characters_events [ + :character_added, + :character_removed, + :character_updated, + :characters_updated, + :present_characters_updated + ] + + @map_characters_ui_events [ + "add_character", + "toggle_track", + "hide_tracking" + ] + + @map_system_events [ + :add_system, + :update_system, + :systems_removed, + :maybe_select_system, + :kills_updated + ] + + @map_system_ui_events [ + "add_hub", + "delete_hub", + "add_system", + "delete_systems", + "manual_add_system", + "get_system_static_infos", + "update_system_position", + "update_system_positions", + "update_system_name", + "update_system_description", + "update_system_labels", + "update_system_locked", + "update_system_tag", + "update_system_status" + ] + + @map_connection_events [ + :add_connection, + :remove_connections, + :update_connection + ] + + @map_connection_ui_events [ + "manual_add_connection", + "manual_delete_connection", + "get_passages", + "update_connection_time_status", + "update_connection_mass_status", + "update_connection_ship_size_type", + "update_connection_locked", + "update_connection_custom_info" + ] + + @map_activity_events [ + :character_activity + ] + + @map_activity_ui_events [ + "show_activity", + "hide_activity" + ] + + @map_routes_events [ + :routes + ] + + @map_routes_ui_events [ + "get_routes", + "set_autopilot_waypoint" + ] + + @map_signatures_events [ + :maybe_link_signature, + :signatures_updated + ] + + @map_signatures_ui_events [ + "update_signatures", + "get_signatures", + "link_signature_to_system", + "unlink_signature" + ] + + def handle_event(socket, %{event: event_name} = event) + when event_name in @map_characters_events, + do: MapCharactersEventHandler.handle_server_event(event, socket) + + def handle_event(socket, %{event: event_name} = event) + when event_name in @map_system_events, + do: MapSystemsEventHandler.handle_server_event(event, socket) + + def handle_event(socket, %{event: event_name} = event) + when event_name in @map_connection_events, + do: MapConnectionsEventHandler.handle_server_event(event, socket) + + def handle_event(socket, %{event: event_name} = event) + when event_name in @map_activity_events, + do: MapActivityEventHandler.handle_server_event(event, socket) + + def handle_event(socket, %{event: event_name} = event) + when event_name in @map_routes_events, + do: MapRoutesEventHandler.handle_server_event(event, socket) + + def handle_event(socket, %{event: event_name} = event) + when event_name in @map_signatures_events, + do: MapSignaturesEventHandler.handle_server_event(event, socket) + + def handle_event(socket, {ref, result}) when is_reference(ref) do + Process.demonitor(ref, [:flush]) + + case result do + {:map_error, map_error} -> + Process.send_after(self(), map_error, 100) + socket + + {event, payload} -> + Process.send_after( + self(), + %{ + event: event, + payload: payload + }, + 10 + ) + + socket + + _ -> + socket + end + end + + def handle_event(socket, event), + do: MapCoreEventHandler.handle_server_event(event, socket) + + def handle_ui_event(event, body, socket) + when event in @map_characters_ui_events, + do: MapSystemsEventHandler.handle_ui_event(event, body, socket) + + def handle_ui_event(event, body, socket) + when event in @map_system_ui_events, + do: MapSystemsEventHandler.handle_ui_event(event, body, socket) + + def handle_ui_event(event, body, socket) + when event in @map_connection_ui_events, + do: MapConnectionsEventHandler.handle_ui_event(event, body, socket) + + def handle_ui_event(event, body, socket) + when event in @map_routes_ui_events, + do: MapRoutesEventHandler.handle_ui_event(event, body, socket) + + def handle_ui_event(event, body, socket) + when event in @map_signatures_ui_events, + do: MapSignaturesEventHandler.handle_ui_event(event, body, socket) + + def handle_ui_event(event, body, socket) + when event in @map_activity_ui_events, + do: MapActivityEventHandler.handle_ui_event(event, body, socket) + + def handle_ui_event(event, body, socket), + do: MapCoreEventHandler.handle_ui_event(event, body, socket) + + def get_system_static_info(nil), do: nil + + def get_system_static_info(solar_system_id) do + case WandererApp.CachedInfo.get_system_static_info(solar_system_id) do + {:ok, system_static_info} -> + map_ui_system_static_info(system_static_info) + + _ -> + %{} + end + end + + def push_map_event(socket, type, body), + do: + socket + |> Phoenix.LiveView.Utils.push_event("map_event", %{ + type: type, + body: body + }) + + def map_ui_character_stat(character), + do: + character + |> Map.take([ + :eve_id, + :name, + :corporation_ticker, + :alliance_ticker + ]) + + def map_ui_connection( + %{ + solar_system_source: solar_system_source, + solar_system_target: solar_system_target, + mass_status: mass_status, + time_status: time_status, + ship_size_type: ship_size_type, + locked: locked + } = _connection + ), + do: %{ + id: "#{solar_system_source}_#{solar_system_target}", + mass_status: mass_status, + time_status: time_status, + ship_size_type: ship_size_type, + locked: locked, + source: "#{solar_system_source}", + target: "#{solar_system_target}" + } + + def map_ui_system( + %{ + solar_system_id: solar_system_id, + name: name, + description: description, + position_x: position_x, + position_y: position_y, + locked: locked, + tag: tag, + labels: labels, + status: status, + visible: visible + } = _system, + _include_static_data? \\ true + ) do + system_static_info = get_system_static_info(solar_system_id) + + %{ + id: "#{solar_system_id}", + position: %{x: position_x, y: position_y}, + description: description, + name: name, + system_static_info: system_static_info, + labels: labels, + locked: locked, + status: status, + tag: tag, + visible: visible + } + end + + def map_ui_system_static_info(nil), do: %{} + + def map_ui_system_static_info(system_static_info), + do: + system_static_info + |> Map.take([ + :region_id, + :constellation_id, + :solar_system_id, + :solar_system_name, + :solar_system_name_lc, + :constellation_name, + :region_name, + :system_class, + :security, + :type_description, + :class_title, + :is_shattered, + :effect_name, + :effect_power, + :statics, + :wandering, + :triglavian_invasion_status, + :sun_type_id + ]) + + def map_ui_kill({solar_system_id, kills}), + do: %{solar_system_id: solar_system_id, kills: kills} + + def map_ui_kill(_kill), do: %{} +end diff --git a/lib/wanderer_app_web/live/maps/map_live.ex b/lib/wanderer_app_web/live/maps/map_live.ex index 79f8c681..0cdac8a4 100644 --- a/lib/wanderer_app_web/live/maps/map_live.ex +++ b/lib/wanderer_app_web/live/maps/map_live.ex @@ -5,8 +5,8 @@ defmodule WandererAppWeb.MapLive do require Logger @impl true - def mount(%{"slug" => map_slug} = params, _session, socket) when is_connected?(socket) do - Process.send_after(self(), {:load_map, map_slug}, Enum.random(10..500)) + def mount(%{"slug" => map_slug} = _params, _session, socket) when is_connected?(socket) do + Process.send_after(self(), %{event: :load_map}, Enum.random(10..500)) {:ok, socket @@ -38,9 +38,8 @@ defmodule WandererAppWeb.MapLive do end @impl true - def handle_params(params, _url, socket) do - {:noreply, apply_action(socket, socket.assigns.live_action, params)} - end + def handle_params(params, _url, socket), + do: {:noreply, apply_action(socket, socket.assigns.live_action, params)} @impl true def handle_info( @@ -48,362 +47,19 @@ defmodule WandererAppWeb.MapLive do %{assigns: %{map_id: map_id}} = socket ) do Phoenix.PubSub.unsubscribe(WandererApp.PubSub, map_id) - {:noreply, push_navigate(socket, to: ~p"/#{map_slug}")} + {:noreply, socket |> push_navigate(to: ~p"/#{map_slug}")} end - @impl true - def handle_info( - %{event: :map_server_started}, - socket - ), - do: {:noreply, socket |> handle_map_server_started()} - @impl true def handle_info(:character_token_invalid, socket), do: {:noreply, socket - |> _put_invalid_token_message()} - - @impl true - def handle_info(%{event: :add_system, payload: system}, socket), - do: - {:noreply, - socket - |> push_map_event("add_systems", [map_ui_system(system)])} - - @impl true - def handle_info(%{event: :update_system, payload: system}, socket), - do: - {:noreply, - socket - |> push_map_event("update_systems", [map_ui_system(system)])} - - @impl true - def handle_info(%{event: :update_connection, payload: connection}, socket), - do: - {:noreply, - socket - |> push_map_event("update_connection", map_ui_connection(connection))} - - @impl true - def handle_info(%{event: :systems_removed, payload: solar_system_ids}, socket), - do: - {:noreply, - socket - |> push_map_event("remove_systems", solar_system_ids)} - - @impl true - def handle_info(%{event: :remove_connections, payload: connections}, socket) do - connection_ids = connections |> Enum.map(&map_ui_connection/1) |> Enum.map(& &1.id) - - {:noreply, - socket - |> push_map_event( - "remove_connections", - connection_ids - )} - end - - @impl true - def handle_info(%{event: :add_connection, payload: connection}, socket) do - connections = [map_ui_connection(connection)] - - {:noreply, - socket - |> push_map_event( - "add_connections", - connections - )} - end - - @impl true - def handle_info( - %{ - event: :maybe_select_system, - payload: %{ - character_id: character_id, - solar_system_id: solar_system_id - } - }, - %{assigns: %{current_user: current_user, map_user_settings: map_user_settings}} = socket - ) do - is_user_character = - current_user.characters |> Enum.map(& &1.id) |> Enum.member?(character_id) - - is_select_on_spash = - map_user_settings - |> WandererApp.MapUserSettingsRepo.to_form_data!() - |> WandererApp.MapUserSettingsRepo.get_boolean_setting("select_on_spash") - - socket = - (is_user_character && is_select_on_spash) - |> case do - true -> - socket - |> push_map_event("select_system", solar_system_id) - - false -> - socket - end - - {:noreply, socket} - end - - @impl true - def handle_info( - %{ - event: :maybe_link_signature, - payload: %{ - character_id: character_id, - solar_system_source: solar_system_source, - solar_system_target: solar_system_target - } - }, - %{ - assigns: %{ - current_user: current_user, - map_id: map_id, - map_user_settings: map_user_settings - } - } = socket - ) do - is_user_character = - current_user.characters |> Enum.map(& &1.id) |> Enum.member?(character_id) - - is_link_signature_on_splash = - map_user_settings - |> WandererApp.MapUserSettingsRepo.to_form_data!() - |> WandererApp.MapUserSettingsRepo.get_boolean_setting("link_signature_on_splash") - - {:ok, signatures} = - WandererApp.Api.MapSystem.read_by_map_and_solar_system(%{ - map_id: map_id, - solar_system_id: solar_system_source - }) - |> case do - {:ok, system} -> - {:ok, get_system_signatures(system.id)} - - _ -> - {:ok, []} - end - - socket = - (is_user_character && is_link_signature_on_splash && not (signatures |> Enum.empty?())) - |> case do - true -> - socket - |> push_map_event("link_signature_to_system", %{ - solar_system_source: solar_system_source, - solar_system_target: solar_system_target - }) - - false -> - socket - end - - {:noreply, socket} - end - - @impl true - def handle_info(%{event: :update_map, payload: map_diff}, socket), - do: - {:noreply, - socket - |> push_map_event( - "map_updated", - map_diff + |> put_flash( + :error, + "One of your characters has expired token. Please refresh it on characters page." )} - @impl true - def handle_info(%{event: :kills_updated, payload: kills}, socket) do - kills = - kills - |> Enum.map(&map_ui_kill/1) - - {:noreply, - socket - |> push_map_event( - "kills_updated", - kills - )} - end - - @impl true - def handle_info( - %{event: :characters_updated}, - %{ - assigns: %{ - map_id: map_id - } - } = socket - ) do - characters = - map_id - |> WandererApp.Map.list_characters() - |> Enum.map(&map_ui_character/1) - - {:noreply, - socket - |> push_map_event( - "characters_updated", - characters - )} - end - - @impl true - def handle_info(%{event: :character_added, payload: character}, socket) do - {:noreply, - socket - |> push_map_event( - "character_added", - character |> map_ui_character() - )} - end - - @impl true - def handle_info(%{event: :character_removed, payload: character}, socket) do - {:noreply, - socket - |> push_map_event( - "character_removed", - character |> map_ui_character() - )} - end - - @impl true - def handle_info(%{event: :character_updated, payload: character}, socket) do - {:noreply, - socket - |> push_map_event( - "character_updated", - character |> map_ui_character() - )} - end - - @impl true - def handle_info( - %{event: :present_characters_updated, payload: present_character_eve_ids}, - socket - ), - do: - {:noreply, - socket - |> push_map_event( - "present_characters", - present_character_eve_ids - )} - - @impl true - def handle_info(%{event: "presence_diff", payload: _payload}, socket), do: {:noreply, socket} - - @impl true - def handle_info(:update_permissions, socket) do - DebounceAndThrottle.Debounce.apply( - Process, - :send_after, - [self(), :refresh_permissions, 100], - "update_permissions_#{inspect(self())}", - 1000 - ) - - {:noreply, socket} - end - - @impl true - def handle_info( - :refresh_permissions, - %{assigns: %{current_user: current_user, map_slug: map_slug}} = socket - ) do - {:ok, %{id: map_id, user_permissions: user_permissions, owner_id: owner_id}} = - map_slug - |> WandererApp.Api.Map.get_map_by_slug!() - |> Ash.load(:user_permissions, actor: current_user) - - user_permissions = - WandererApp.Permissions.get_map_permissions( - user_permissions, - owner_id, - current_user.characters |> Enum.map(& &1.id) - ) - - socket = - case user_permissions do - %{view_system: false} -> - socket - |> put_flash(:error, "Your access to the map have been revoked.") - |> push_navigate(to: ~p"/maps") - - %{track_character: track_character} -> - {:ok, map_characters} = - case WandererApp.Api.MapCharacterSettings.tracked_by_map(%{ - map_id: map_id, - character_ids: current_user.characters |> Enum.map(& &1.id) - }) do - {:ok, settings} -> - {:ok, - settings - |> Enum.map(fn s -> s |> Ash.load!(:character) |> Map.get(:character) end)} - - _ -> - {:ok, []} - end - - case track_character do - false -> - :ok = _untrack_characters(map_characters, map_id) - :ok = _remove_characters(map_characters, map_id) - - _ -> - :ok = _track_characters(map_characters, map_id, true) - :ok = _add_characters(map_characters, map_id, track_character) - end - - socket - |> assign(user_permissions: user_permissions) - |> push_map_event( - "user_permissions", - user_permissions - ) - end - - {:noreply, socket} - end - - def handle_info({:load_map, map_slug}, %{assigns: %{current_user: current_user}} = socket) do - ErrorTracker.set_context(%{user_id: current_user.id}) - - map_slug - |> WandererApp.MapRepo.get_by_slug_with_permissions(current_user) - |> case do - {:ok, map} -> - {:noreply, socket |> init_map(map)} - - {:error, _} -> - {:noreply, - socket - |> put_flash( - :error, - "Something went wrong. Please try one more time or submit an issue." - ) - |> push_navigate(to: ~p"/maps")} - end - end - - @impl true - def handle_info( - %{event: :signatures_updated, payload: solar_system_id}, - socket - ), - do: - {:noreply, - socket - |> push_map_event( - "signatures_updated", - solar_system_id - )} - def handle_info(:no_access, socket), do: {:noreply, @@ -429,1004 +85,15 @@ defmodule WandererAppWeb.MapLive do |> push_navigate(to: ~p"/tracking/#{map_slug}")} @impl true - def handle_info( - {ref, result}, - socket - ) - when is_reference(ref) do - Process.demonitor(ref, [:flush]) - - case result do - {:map_error, map_error} -> - Process.send_after(self(), map_error, 100) - {:noreply, socket} - - {:character_activity, character_activity} -> - {:noreply, - socket - |> assign(:character_activity, character_activity)} - - {:routes, {solar_system_id, %{routes: routes, systems_static_data: systems_static_data}}} -> - {:noreply, - socket - |> push_map_event( - "routes", - %{ - solar_system_id: solar_system_id, - loading: false, - routes: routes, - systems_static_data: systems_static_data - } - )} - - _ -> - {:noreply, socket} - end - end + def handle_info(info, socket), + do: + {:noreply, + socket + |> WandererAppWeb.MapEventHandler.handle_event(info)} @impl true - def handle_info(_event, socket), do: {:noreply, socket} - - @impl true - def handle_event("ui_loaded", _body, %{assigns: %{map_slug: map_slug} = assigns} = socket) do - assigns - |> Map.get(:map_id) - |> case do - map_id when not is_nil(map_id) -> - maybe_start_map(map_id) - - _ -> - WandererApp.Cache.insert("map_#{map_slug}:ui_loaded", true) - end - - {:noreply, socket} - end - - @impl true - def handle_event( - "manual_add_system", - %{"coordinates" => coordinates} = _event, - %{ - assigns: %{ - has_tracked_characters?: true, - map_slug: map_slug, - user_permissions: %{add_system: true} - } - } = - socket - ), - do: - {:noreply, - socket - |> assign(coordinates: coordinates) - |> push_patch(to: ~p"/#{map_slug}/add-system")} - - @impl true - def handle_event( - "manual_add_connection", - %{"source" => solar_system_source_id, "target" => solar_system_target_id} = _event, - %{ - assigns: %{ - map_id: map_id, - current_user: current_user, - tracked_character_ids: tracked_character_ids, - has_tracked_characters?: true, - user_permissions: %{add_connection: true} - } - } = - socket - ) do - map_id - |> WandererApp.Map.Server.add_connection(%{ - solar_system_source_id: solar_system_source_id |> String.to_integer(), - solar_system_target_id: solar_system_target_id |> String.to_integer() - }) - - {:ok, _} = - WandererApp.User.ActivityTracker.track_map_event(:map_connection_added, %{ - character_id: tracked_character_ids |> List.first(), - user_id: current_user.id, - map_id: map_id, - solar_system_source_id: "#{solar_system_source_id}" |> String.to_integer(), - solar_system_target_id: "#{solar_system_target_id}" |> String.to_integer() - }) - - {:noreply, socket} - end - - @impl true - def handle_event( - "add_hub", - %{"system_id" => solar_system_id} = _event, - %{ - assigns: %{ - map_id: map_id, - current_user: current_user, - tracked_character_ids: tracked_character_ids, - has_tracked_characters?: true, - user_permissions: %{update_system: true} - } - } = - socket - ) do - map_id - |> WandererApp.Map.Server.add_hub(%{ - solar_system_id: solar_system_id - }) - - {:ok, _} = - WandererApp.User.ActivityTracker.track_map_event(:hub_added, %{ - character_id: tracked_character_ids |> List.first(), - user_id: current_user.id, - map_id: map_id, - solar_system_id: solar_system_id - }) - - {:noreply, socket} - end - - @impl true - def handle_event( - "delete_hub", - %{"system_id" => solar_system_id} = _event, - %{ - assigns: %{ - map_id: map_id, - current_user: current_user, - tracked_character_ids: tracked_character_ids, - has_tracked_characters?: true, - user_permissions: %{update_system: true} - } - } = - socket - ) do - map_id - |> WandererApp.Map.Server.remove_hub(%{ - solar_system_id: solar_system_id - }) - - {:ok, _} = - WandererApp.User.ActivityTracker.track_map_event(:hub_removed, %{ - character_id: tracked_character_ids |> List.first(), - user_id: current_user.id, - map_id: map_id, - solar_system_id: solar_system_id - }) - - {:noreply, socket} - end - - @impl true - def handle_event( - "update_system_" <> param, - %{"system_id" => solar_system_id, "value" => value} = _event, - %{ - assigns: %{ - map_id: map_id, - current_user: current_user, - tracked_character_ids: tracked_character_ids, - has_tracked_characters?: true, - user_permissions: %{update_system: true} - } - } = - socket - ) do - method_atom = - case param do - "name" -> :update_system_name - "description" -> :update_system_description - "labels" -> :update_system_labels - "locked" -> :update_system_locked - "tag" -> :update_system_tag - "status" -> :update_system_status - _ -> nil - end - - key_atom = - case param do - "name" -> :name - "description" -> :description - "labels" -> :labels - "locked" -> :locked - "tag" -> :tag - "status" -> :status - _ -> :none - end - - apply(WandererApp.Map.Server, method_atom, [ - map_id, - %{ - solar_system_id: "#{solar_system_id}" |> String.to_integer() - } - |> Map.put_new(key_atom, value) - ]) - - {:ok, _} = - WandererApp.User.ActivityTracker.track_map_event(:system_updated, %{ - character_id: tracked_character_ids |> List.first(), - user_id: current_user.id, - map_id: map_id, - solar_system_id: "#{solar_system_id}" |> String.to_integer(), - key: key_atom, - value: value - }) - - {:noreply, socket} - end - - @impl true - def handle_event( - "update_connection_" <> param, - %{ - "source" => solar_system_source_id, - "target" => solar_system_target_id, - "value" => value - } = _event, - %{ - assigns: %{ - map_id: map_id, - current_user: current_user, - tracked_character_ids: tracked_character_ids, - has_tracked_characters?: true, - user_permissions: %{update_system: true} - } - } = - socket - ) do - method_atom = - case param do - "time_status" -> :update_connection_time_status - "mass_status" -> :update_connection_mass_status - "ship_size_type" -> :update_connection_ship_size_type - "locked" -> :update_connection_locked - "custom_info" -> :update_connection_custom_info - _ -> nil - end - - key_atom = - case param do - "time_status" -> :time_status - "mass_status" -> :mass_status - "ship_size_type" -> :ship_size_type - "locked" -> :locked - "custom_info" -> :custom_info - _ -> nil - end - - {:ok, _} = - WandererApp.User.ActivityTracker.track_map_event(:map_connection_updated, %{ - character_id: tracked_character_ids |> List.first(), - user_id: current_user.id, - map_id: map_id, - solar_system_source_id: "#{solar_system_source_id}" |> String.to_integer(), - solar_system_target_id: "#{solar_system_target_id}" |> String.to_integer(), - key: key_atom, - value: value - }) - - apply(WandererApp.Map.Server, method_atom, [ - map_id, - %{ - solar_system_source_id: "#{solar_system_source_id}" |> String.to_integer(), - solar_system_target_id: "#{solar_system_target_id}" |> String.to_integer() - } - |> Map.put_new(key_atom, value) - ]) - - {:noreply, socket} - end - - @impl true - def handle_event( - "update_signatures", - %{ - "system_id" => solar_system_id, - "added" => added_signatures, - "updated" => updated_signatures, - "removed" => removed_signatures - } = _event, - %{ - assigns: %{ - map_id: map_id, - map_user_settings: map_user_settings, - user_characters: user_characters, - user_permissions: %{update_system: true} - } - } = socket - ) do - WandererApp.Api.MapSystem.read_by_map_and_solar_system(%{ - map_id: map_id, - solar_system_id: solar_system_id |> String.to_integer() - }) - |> case do - {:ok, system} -> - first_character_eve_id = - user_characters |> List.first() - - case not is_nil(first_character_eve_id) do - true -> - added_signatures = - added_signatures - |> parse_signatures(first_character_eve_id, system.id) - - updated_signatures = - updated_signatures - |> parse_signatures(first_character_eve_id, system.id) - - updated_signatures_eve_ids = - updated_signatures - |> Enum.map(fn s -> s.eve_id end) - - removed_signatures_eve_ids = - removed_signatures - |> parse_signatures(first_character_eve_id, system.id) - |> Enum.map(fn s -> s.eve_id end) - - delete_connection_with_sigs = - map_user_settings - |> WandererApp.MapUserSettingsRepo.to_form_data!() - |> WandererApp.MapUserSettingsRepo.get_boolean_setting( - "delete_connection_with_sigs" - ) - - WandererApp.Api.MapSystemSignature.by_system_id!(system.id) - |> Enum.filter(fn s -> s.eve_id in removed_signatures_eve_ids end) - |> Enum.each(fn s -> - if delete_connection_with_sigs && not is_nil(s.linked_system_id) do - map_id - |> WandererApp.Map.Server.delete_connection(%{ - solar_system_source_id: solar_system_id |> String.to_integer(), - solar_system_target_id: s.linked_system_id - }) - end - - s - |> Ash.destroy!() - end) - - WandererApp.Api.MapSystemSignature.by_system_id!(system.id) - |> Enum.filter(fn s -> s.eve_id in updated_signatures_eve_ids end) - |> Enum.each(fn s -> - updated = updated_signatures |> Enum.find(fn u -> u.eve_id == s.eve_id end) - - if not is_nil(updated) do - s - |> WandererApp.Api.MapSystemSignature.update(updated) - end - end) - - added_signatures - |> Enum.map(fn s -> - s |> WandererApp.Api.MapSystemSignature.create!() - end) - - Phoenix.PubSub.broadcast!(WandererApp.PubSub, map_id, %{ - event: :signatures_updated, - payload: system.solar_system_id - }) - - {:reply, %{signatures: get_system_signatures(system.id)}, socket} - - _ -> - {:reply, %{signatures: []}, - socket - |> put_flash( - :error, - "You should enable tracking for at least one character to work with signatures." - )} - end - - _ -> - {:noreply, socket} - end - end - - @impl true - def handle_event( - "get_signatures", - %{"system_id" => solar_system_id} = _event, - %{ - assigns: %{ - map_id: map_id - } - } = socket - ) do - case WandererApp.Api.MapSystem.read_by_map_and_solar_system(%{ - map_id: map_id, - solar_system_id: solar_system_id |> String.to_integer() - }) do - {:ok, system} -> - {:reply, %{signatures: get_system_signatures(system.id)}, socket} - - _ -> - {:reply, %{signatures: []}, socket} - end - end - - @impl true - def handle_event( - "get_system_static_infos", - %{"solar_system_ids" => solar_system_ids} = _event, - socket - ) do - system_static_infos = - solar_system_ids - |> Enum.map(&WandererApp.CachedInfo.get_system_static_info!/1) - |> Enum.map(&map_ui_system_static_info/1) - - {:reply, %{system_static_infos: system_static_infos}, socket} - end - - @impl true - def handle_event( - "add_system", - %{"system_id" => solar_system_id} = _event, - %{ - assigns: - %{ - map_id: map_id, - map_slug: map_slug, - current_user: current_user, - tracked_character_ids: tracked_character_ids, - user_permissions: %{add_system: true} - } = assigns - } = socket - ) - when is_binary(solar_system_id) and solar_system_id != "" do - coordinates = Map.get(assigns, :coordinates) - - WandererApp.Map.Server.add_system( - map_id, - %{ - solar_system_id: solar_system_id |> String.to_integer(), - coordinates: coordinates - }, - current_user.id, - tracked_character_ids |> List.first() - ) - - {:noreply, - socket - |> push_patch(to: ~p"/#{map_slug}")} - end - - @impl true - def handle_event("get_passages", %{"from" => from, "to" => to} = _event, socket) do - {:ok, passages} = socket |> map_id() |> _get_connection_passages(from, to) - - {:reply, passages, socket} - end - - @impl true - def handle_event( - "get_routes", - %{"system_id" => solar_system_id, "routes_settings" => routes_settings} = _event, - %{assigns: %{map_id: map_id, map_loaded?: true}} = socket - ) do - Task.async(fn -> - {:ok, hubs} = map_id |> WandererApp.Map.list_hubs() - - {:ok, routes} = - WandererApp.Maps.find_routes( - map_id, - hubs, - solar_system_id, - _get_routes_settings(routes_settings) - ) - - {:routes, {solar_system_id, routes}} - end) - - {:noreply, socket} - end - - @impl true - def handle_event( - "update_system_position", - position, - %{ - assigns: %{ - map_id: map_id, - has_tracked_characters?: true, - user_permissions: %{update_system: true} - } - } = socket - ) do - map_id - |> _update_system_position(position) - - {:noreply, socket} - end - - @impl true - def handle_event( - "update_system_positions", - positions, - %{ - assigns: %{ - map_id: map_id, - has_tracked_characters?: true, - user_permissions: %{update_system: true} - } - } = socket - ) do - map_id - |> _update_system_positions(positions) - - {:noreply, socket} - end - - @impl true - def handle_event( - "delete_systems", - solar_system_ids, - %{ - assigns: %{ - map_id: map_id, - current_user: current_user, - tracked_character_ids: tracked_character_ids, - has_tracked_characters?: true, - user_permissions: %{delete_system: true} - } - } = - socket - ) do - map_id - |> WandererApp.Map.Server.delete_systems( - solar_system_ids |> Enum.map(&String.to_integer/1), - current_user.id, - tracked_character_ids |> List.first() - ) - - {:noreply, socket} - end - - @impl true - def handle_event( - "manual_delete_connection", - %{"source" => solar_system_source_id, "target" => solar_system_target_id} = _event, - %{ - assigns: %{ - map_id: map_id, - current_user: current_user, - tracked_character_ids: tracked_character_ids, - has_tracked_characters?: true, - user_permissions: %{delete_connection: true} - } - } = - socket - ) do - map_id - |> WandererApp.Map.Server.delete_connection(%{ - solar_system_source_id: solar_system_source_id |> String.to_integer(), - solar_system_target_id: solar_system_target_id |> String.to_integer() - }) - - {:ok, _} = - WandererApp.User.ActivityTracker.track_map_event(:map_connection_removed, %{ - character_id: tracked_character_ids |> List.first(), - user_id: current_user.id, - map_id: map_id, - solar_system_source_id: "#{solar_system_source_id}" |> String.to_integer(), - solar_system_target_id: "#{solar_system_target_id}" |> String.to_integer() - }) - - {:noreply, socket} - end - - @impl true - def handle_event( - "set_autopilot_waypoint", - %{ - "character_eve_ids" => character_eve_ids, - "add_to_beginning" => add_to_beginning, - "clear_other_waypoints" => clear_other_waypoints, - "destination_id" => destination_id - } = _event, - %{assigns: %{current_user: current_user, has_tracked_characters?: true}} = socket - ) do - character_eve_ids - |> Task.async_stream(fn character_eve_id -> - set_autopilot_waypoint( - current_user, - character_eve_id, - add_to_beginning, - clear_other_waypoints, - destination_id - ) - end) - |> Enum.map(fn _result -> :skip end) - - {:noreply, socket} - end - - @impl true - def handle_event( - "live_select_change", - %{"id" => id, "text" => text}, - socket - ) - when id == "_system_id_live_select_component" do - options = - WandererApp.Api.MapSolarSystem.find_by_name!(%{name: text}) - |> Enum.take(100) - |> Enum.map(&map_system/1) - - send_update(LiveSelect.Component, options: options, id: id) - - {:noreply, socket} - end - - @impl true - def handle_event( - "add_character", - _, - %{ - assigns: %{ - current_user: current_user, - map_id: map_id, - user_permissions: %{track_character: true} - } - } = socket - ) do - {:ok, character_settings} = - case WandererApp.Api.MapCharacterSettings.read_by_map(%{map_id: map_id}) do - {:ok, settings} -> {:ok, settings} - _ -> {:ok, []} - end - - {:noreply, - socket - |> assign( - show_tracking?: true, - character_settings: character_settings - ) - |> assign_async(:characters, fn -> - {:ok, map} = - map_id - |> WandererApp.MapRepo.get([:acls]) - - map - |> WandererApp.Maps.load_characters( - character_settings, - current_user.id - ) - end)} - end - - @impl true - def handle_event( - "add_character", - _, - %{ - assigns: %{ - user_permissions: %{track_character: false} - } - } = socket - ), - do: - {:noreply, - socket - |> put_flash( - :error, - "You don't have permissions to track characters. Please contact administrator." - )} - - @impl true - def handle_event("toggle_track_" <> character_id, _, socket) do - handle_event("toggle_track", %{"character-id" => character_id}, socket) - end - - @impl true - def handle_event( - "toggle_track", - %{"character-id" => character_id}, - %{ - assigns: %{ - map_id: map_id, - map_slug: map_slug, - character_settings: character_settings, - current_user: current_user, - only_tracked_characters: only_tracked_characters - } - } = socket - ) do - socket = - case character_settings |> Enum.find(&(&1.character_id == character_id)) do - nil -> - {:ok, map_character_settings} = - WandererApp.Api.MapCharacterSettings.create(%{ - character_id: character_id, - map_id: map_id, - tracked: true - }) - - character = map_character_settings |> Ash.load!(:character) |> Map.get(:character) - - :ok = _track_characters([character], map_id, true) - :ok = _add_characters([character], map_id, true) - - socket - - character_setting -> - case character_setting.tracked do - true -> - {:ok, map_character_settings} = - character_setting - |> WandererApp.Api.MapCharacterSettings.untrack() - - character = map_character_settings |> Ash.load!(:character) |> Map.get(:character) - - :ok = _untrack_characters([character], map_id) - :ok = _remove_characters([character], map_id) - - if only_tracked_characters do - socket - |> put_flash( - :error, - "You should enable tracking for all characters that have access to this map first!" - ) - |> push_navigate(to: ~p"/tracking/#{map_slug}") - else - socket - end - - _ -> - {:ok, map_character_settings} = - character_setting - |> WandererApp.Api.MapCharacterSettings.track() - - character = map_character_settings |> Ash.load!(:character) |> Map.get(:character) - - :ok = _track_characters([character], map_id, true) - :ok = _add_characters([character], map_id, true) - - socket - end - end - - %{result: characters} = socket.assigns.characters - - {:ok, map_characters} = _get_tracked_map_characters(map_id, current_user) - - user_character_eve_ids = map_characters |> Enum.map(& &1.eve_id) - - {:ok, character_settings} = - case WandererApp.Api.MapCharacterSettings.read_by_map(%{map_id: map_id}) do - {:ok, settings} -> {:ok, settings} - _ -> {:ok, []} - end - - characters = - characters - |> Enum.map(fn c -> - WandererApp.Maps.map_character( - c, - character_settings |> Enum.find(&(&1.character_id == c.id)) - ) - end) - - {:noreply, - socket - |> assign(user_characters: user_character_eve_ids) - |> assign(has_tracked_characters?: _has_tracked_characters?(user_character_eve_ids)) - |> assign(character_settings: character_settings) - |> assign_async(:characters, fn -> - {:ok, %{characters: characters}} - end) - |> push_map_event( - "init", - %{ - user_characters: user_character_eve_ids, - reset: false - } - )} - end - - @impl true - def handle_event( - "get_user_settings", - _, - %{assigns: %{map_id: map_id, current_user: current_user}} = socket - ) do - {:ok, user_settings} = - WandererApp.MapUserSettingsRepo.get!(map_id, current_user.id) - |> WandererApp.MapUserSettingsRepo.to_form_data() - - {:reply, %{user_settings: user_settings}, socket} - end - - @impl true - def handle_event( - "update_user_settings", - user_settings_form, - %{assigns: %{map_id: map_id, current_user: current_user}} = socket - ) do - settings = - user_settings_form - |> Map.take(["select_on_spash", "link_signature_on_splash", "delete_connection_with_sigs"]) - |> Jason.encode!() - - {:ok, user_settings} = - WandererApp.MapUserSettingsRepo.create_or_update(map_id, current_user.id, settings) - - {:noreply, - socket |> assign(user_settings_form: user_settings_form, map_user_settings: user_settings)} - end - - @impl true - def handle_event( - "link_signature_to_system", - %{ - "signature_eve_id" => signature_eve_id, - "solar_system_source" => solar_system_source, - "solar_system_target" => solar_system_target - }, - %{ - assigns: %{ - map_id: map_id, - user_characters: user_characters, - user_permissions: %{update_system: true} - } - } = socket - ) do - case WandererApp.Api.MapSystem.read_by_map_and_solar_system(%{ - map_id: map_id, - solar_system_id: solar_system_source - }) do - {:ok, system} -> - first_character_eve_id = - user_characters |> List.first() - - case not is_nil(first_character_eve_id) do - true -> - WandererApp.Api.MapSystemSignature.by_system_id!(system.id) - |> Enum.filter(fn s -> s.eve_id == signature_eve_id end) - |> Enum.each(fn s -> - s - |> WandererApp.Api.MapSystemSignature.update_linked_system(%{ - linked_system_id: solar_system_target - }) - end) - - Phoenix.PubSub.broadcast!(WandererApp.PubSub, map_id, %{ - event: :signatures_updated, - payload: solar_system_source - }) - - {:noreply, socket} - - _ -> - {:noreply, - socket - |> put_flash( - :error, - "You should enable tracking for at least one character to work with signatures." - )} - end - - _ -> - {:noreply, socket} - end - end - - @impl true - def handle_event( - "unlink_signature", - %{ - "signature_eve_id" => signature_eve_id, - "solar_system_source" => solar_system_source - }, - %{ - assigns: %{ - map_id: map_id, - user_characters: user_characters, - user_permissions: %{update_system: true} - } - } = socket - ) do - case WandererApp.Api.MapSystem.read_by_map_and_solar_system(%{ - map_id: map_id, - solar_system_id: solar_system_source - }) do - {:ok, system} -> - first_character_eve_id = - user_characters |> List.first() - - case not is_nil(first_character_eve_id) do - true -> - WandererApp.Api.MapSystemSignature.by_system_id!(system.id) - |> Enum.filter(fn s -> s.eve_id == signature_eve_id end) - |> Enum.each(fn s -> - s - |> WandererApp.Api.MapSystemSignature.update_linked_system(%{ - linked_system_id: nil - }) - end) - - Phoenix.PubSub.broadcast!(WandererApp.PubSub, map_id, %{ - event: :signatures_updated, - payload: solar_system_source - }) - - {:noreply, socket} - - _ -> - {:noreply, - socket - |> put_flash( - :error, - "You should enable tracking for at least one character to work with signatures." - )} - end - - _ -> - {:noreply, socket} - end - end - - @impl true - def handle_event("show_activity", _, socket) do - Task.async(fn -> - {:ok, character_activity} = socket |> map_id() |> _get_character_activity() - - {:character_activity, character_activity} - end) - - {:noreply, - socket - |> assign(:show_activity?, true)} - end - - @impl true - def handle_event("hide_activity", _, socket), - do: {:noreply, socket |> assign(show_activity?: false)} - - @impl true - def handle_event("hide_tracking", _, socket), - do: {:noreply, socket |> assign(show_tracking?: false)} - - @impl true - def handle_event( - "log_map_error", - %{"componentStack" => component_stack, "error" => error}, - socket - ) do - Logger.error(fn -> "map_ui_error: #{error} \n#{component_stack} " end) - - {:noreply, - socket - |> put_flash(:error, "Something went wrong. Please try refresh page or submit an issue.") - |> push_event("js-exec", %{ - to: "#map-loader", - attr: "data-loading", - timeout: 100 - })} - end - - @impl true - def handle_event("noop", _, socket), do: {:noreply, socket} - - @impl true - def handle_event( - _event, - _body, - %{assigns: %{has_tracked_characters?: false}} = - socket - ), - do: - {:noreply, - socket - |> put_flash( - :error, - "You should enable tracking for at least one character." - )} - - @impl true - def handle_event(event, body, socket) do - Logger.warning(fn -> "unhandled event: #{event} #{inspect(body)}" end) - {:noreply, socket} - end + def handle_event(event, body, socket), + do: WandererAppWeb.MapEventHandler.handle_ui_event(event, body, socket) defp apply_action(socket, :index, _params) do socket @@ -1440,345 +107,6 @@ defmodule WandererAppWeb.MapLive do |> assign(:add_system_form, to_form(%{"system_id" => nil})) end - defp init_map( - %{assigns: %{current_user: current_user, map_slug: map_slug}} = socket, - %{ - id: map_id, - deleted: false, - only_tracked_characters: only_tracked_characters, - user_permissions: user_permissions, - name: map_name, - owner_id: owner_id - } = map - ) do - user_permissions = - WandererApp.Permissions.get_map_permissions( - user_permissions, - owner_id, - current_user.characters |> Enum.map(& &1.id) - ) - - {:ok, character_settings} = - case WandererApp.Api.MapCharacterSettings.read_by_map(%{map_id: map_id}) do - {:ok, settings} -> {:ok, settings} - _ -> {:ok, []} - end - - {:ok, %{characters: availaible_map_characters}} = - WandererApp.Maps.load_characters(map, character_settings, current_user.id) - - can_view? = user_permissions.view_system - can_track? = user_permissions.track_character - - tracked_character_ids = - availaible_map_characters |> Enum.filter(& &1.tracked) |> Enum.map(& &1.id) - - all_character_tracked? = - not (availaible_map_characters |> Enum.empty?()) and - availaible_map_characters |> Enum.all?(& &1.tracked) - - cond do - (only_tracked_characters and can_track? and all_character_tracked?) or - (not only_tracked_characters and can_view?) -> - Phoenix.PubSub.subscribe(WandererApp.PubSub, map_id) - {:ok, ui_loaded} = WandererApp.Cache.get_and_remove("map_#{map_slug}:ui_loaded", false) - - if ui_loaded do - maybe_start_map(map_id) - end - - socket - |> assign( - map_id: map_id, - page_title: map_name, - user_permissions: user_permissions, - tracked_character_ids: tracked_character_ids, - only_tracked_characters: only_tracked_characters - ) - - only_tracked_characters and can_track? and not all_character_tracked? -> - Process.send_after(self(), :not_all_characters_tracked, 10) - socket - - true -> - Process.send_after(self(), :no_permissions, 10) - socket - end - end - - defp init_map(socket, _map) do - Process.send_after(self(), :no_access, 10) - socket - end - - defp maybe_start_map(map_id) do - {:ok, map_server_started} = WandererApp.Cache.lookup("map_#{map_id}:started", false) - - if map_server_started do - Process.send_after(self(), %{event: :map_server_started}, 10) - else - WandererApp.Map.Manager.start_map(map_id) - end - end - - defp handle_map_start_events(socket, map_id, events) do - events - |> Enum.reduce(socket, fn event, socket -> - case event do - {:track_characters, map_characters, track_character} -> - :ok = _track_characters(map_characters, map_id, track_character) - :ok = _add_characters(map_characters, map_id, track_character) - socket - - :invalid_token_message -> - socket - |> put_flash( - :error, - "One of your characters has expired token. Please refresh it on characters page." - ) - - :empty_tracked_characters -> - socket - |> put_flash( - :info, - "You should enable tracking for at least one character to work with map." - ) - - :map_character_limit -> - socket - |> put_flash( - :error, - "Map reached its character limit, your characters won't be tracked. Please contact administrator." - ) - - _ -> - socket - end - end) - end - - defp map_start( - socket, - %{ - map_id: map_id, - map_user_settings: map_user_settings, - user_characters: user_character_eve_ids, - initial_data: initial_data, - events: events - } = _started_data - ) do - socket = - socket - |> handle_map_start_events(map_id, events) - - map_characters = map_id |> WandererApp.Map.list_characters() - - socket - |> assign( - map_loaded?: true, - map_user_settings: map_user_settings, - user_characters: user_character_eve_ids, - has_tracked_characters?: _has_tracked_characters?(user_character_eve_ids) - ) - |> push_map_event( - "init", - initial_data |> Map.put(:characters, map_characters |> Enum.map(&map_ui_character/1)) - ) - |> push_event("js-exec", %{ - to: "#map-loader", - attr: "data-loaded" - }) - end - - defp handle_map_server_started( - %{ - assigns: %{ - current_user: current_user, - map_id: map_id, - user_permissions: - %{view_system: true, track_character: track_character} = user_permissions - } - } = socket - ) do - with {:ok, _} <- current_user |> WandererApp.Api.User.update_last_map(%{last_map_id: map_id}), - {:ok, map_user_settings} <- WandererApp.MapUserSettingsRepo.get(map_id, current_user.id), - {:ok, tracked_map_characters} <- _get_tracked_map_characters(map_id, current_user), - {:ok, characters_limit} <- map_id |> WandererApp.Map.get_characters_limit(), - {:ok, present_character_ids} <- - WandererApp.Cache.lookup("map_#{map_id}:presence_character_ids", []), - {:ok, kills} <- WandererApp.Cache.lookup("map_#{map_id}:zkb_kills", Map.new()) do - user_character_eve_ids = tracked_map_characters |> Enum.map(& &1.eve_id) - - events = - case tracked_map_characters |> Enum.any?(&(&1.access_token == nil)) do - true -> - [:invalid_token_message] - - _ -> - [] - end - - events = - case tracked_map_characters |> Enum.empty?() do - true -> - events ++ [:empty_tracked_characters] - - _ -> - events - end - - events = - case present_character_ids |> Enum.count() < characters_limit do - true -> - events ++ [{:track_characters, tracked_map_characters, track_character}] - - _ -> - events ++ [:map_character_limit] - end - - initial_data = - map_id - |> get_map_data() - |> Map.merge(%{ - kills: - kills - |> Enum.filter(fn {_, kills} -> kills > 0 end) - |> Enum.map(&map_ui_kill/1), - present_characters: - present_character_ids - |> WandererApp.Character.get_character_eve_ids!(), - user_characters: user_character_eve_ids, - user_permissions: user_permissions, - system_static_infos: nil, - wormhole_types: nil, - effects: nil, - reset: false - }) - - system_static_infos = - map_id - |> WandererApp.Map.list_systems!() - |> Enum.map(&WandererApp.CachedInfo.get_system_static_info!(&1.solar_system_id)) - |> Enum.map(&map_ui_system_static_info/1) - - initial_data = - initial_data - |> Map.put( - :wormholes, - WandererApp.CachedInfo.get_wormhole_types!() - ) - |> Map.put( - :effects, - WandererApp.CachedInfo.get_effects!() - ) - |> Map.put( - :system_static_infos, - system_static_infos - ) - |> Map.put(:reset, true) - - socket - |> map_start(%{ - map_id: map_id, - map_user_settings: map_user_settings, - user_characters: user_character_eve_ids, - initial_data: initial_data, - events: events - }) - else - error -> - Logger.error(fn -> "map_start_error: #{error}" end) - Process.send_after(self(), :no_access, 10) - - socket - end - end - - defp handle_map_server_started(socket) do - Process.send_after(self(), :no_access, 10) - socket - end - - defp set_autopilot_waypoint( - current_user, - character_eve_id, - add_to_beginning, - clear_other_waypoints, - destination_id - ) do - case current_user.characters - |> Enum.find(fn c -> c.eve_id == character_eve_id end) do - nil -> - :skip - - %{id: character_id} = _character -> - character_id - |> WandererApp.Character.set_autopilot_waypoint(destination_id, - add_to_beginning: add_to_beginning, - clear_other_waypoints: clear_other_waypoints - ) - - :skip - end - end - - defp get_map_data(map_id, include_static_data? \\ true) do - {:ok, hubs} = map_id |> WandererApp.Map.list_hubs() - {:ok, connections} = map_id |> WandererApp.Map.list_connections() - {:ok, systems} = map_id |> WandererApp.Map.list_systems() - - %{ - systems: systems |> Enum.map(fn system -> map_ui_system(system, include_static_data?) end), - hubs: hubs, - connections: connections |> Enum.map(&map_ui_connection/1) - } - end - - defp _get_tracked_map_characters(map_id, current_user) do - case WandererApp.Api.MapCharacterSettings.tracked_by_map(%{ - map_id: map_id, - character_ids: current_user.characters |> Enum.map(& &1.id) - }) do - {:ok, settings} -> - {:ok, - settings - |> Enum.map(fn s -> s |> Ash.load!(:character) |> Map.get(:character) end)} - - _ -> - {:ok, []} - end - end - - defp _get_character_activity(map_id) do - {:ok, jumps} = WandererApp.Api.MapChainPassages.by_map_id(%{map_id: map_id}) - - jumps = - jumps - |> Enum.map(fn p -> %{p | character: p.character |> map_ui_character_stat()} end) - - {:ok, %{jumps: jumps}} - end - - defp _get_connection_passages(map_id, from, to) do - {:ok, passages} = WandererApp.MapChainPassagesRepo.by_connection(map_id, from, to) - - passages = - passages - |> Enum.map(fn p -> - %{ - p - | character: p.character |> map_ui_character_stat() - } - |> Map.put_new( - :ship, - WandererApp.Character.get_ship(%{ship: p.ship_type_id, ship_name: p.ship_name}) - ) - |> Map.drop([:ship_type_id, :ship_name]) - end) - - {:ok, %{passages: passages}} - end - def character_item(assigns) do ~H"""
@@ -1791,384 +119,4 @@ defmodule WandererAppWeb.MapLive do
""" end - - defp _put_invalid_token_message(socket) do - socket - |> put_flash( - :error, - "One of your characters has expired token. Please refresh it on characters page." - ) - end - - defp get_system_signatures(system_id), - do: - system_id - |> WandererApp.Api.MapSystemSignature.by_system_id!() - |> Enum.map(fn %{updated_at: updated_at, linked_system_id: linked_system_id} = s -> - s - |> Map.take([ - :eve_id, - :name, - :description, - :kind, - :group, - :type, - :updated_at - ]) - |> Map.put(:linked_system, get_system_static_info(linked_system_id)) - |> Map.put(:updated_at, updated_at |> Calendar.strftime("%Y/%m/%d %H:%M:%S")) - end) - - defp _has_tracked_characters?([]), do: false - defp _has_tracked_characters?(_user_characters), do: true - - defp _update_system_positions(_map_id, []), do: :ok - - defp _update_system_positions(map_id, [position | rest]) do - _update_system_position(map_id, position) - _update_system_positions(map_id, rest) - end - - defp _update_system_position(map_id, %{ - "position" => %{"x" => x, "y" => y}, - "solar_system_id" => solar_system_id - }), - do: - map_id - |> WandererApp.Map.Server.update_system_position(%{ - solar_system_id: solar_system_id |> String.to_integer(), - position_x: x, - position_y: y - }) - - def get_character_location(%{location: location} = _character), - do: %{location: location} - - defp map_ui_system( - %{ - solar_system_id: solar_system_id, - name: name, - description: description, - position_x: position_x, - position_y: position_y, - locked: locked, - tag: tag, - labels: labels, - status: status, - visible: visible - } = _system, - _include_static_data? \\ true - ) do - system_static_info = get_system_static_info(solar_system_id) - - %{ - id: "#{solar_system_id}", - position: %{x: position_x, y: position_y}, - description: description, - name: name, - system_static_info: system_static_info, - labels: labels, - locked: locked, - status: status, - tag: tag, - visible: visible - } - end - - defp get_system_static_info(nil), do: nil - - defp get_system_static_info(solar_system_id) do - case WandererApp.CachedInfo.get_system_static_info(solar_system_id) do - {:ok, system_static_info} -> - map_ui_system_static_info(system_static_info) - - _ -> - %{} - end - end - - defp map_ui_system_static_info(nil), do: %{} - - defp map_ui_system_static_info(system_static_info), - do: - system_static_info - |> Map.take([ - :region_id, - :constellation_id, - :solar_system_id, - :solar_system_name, - :solar_system_name_lc, - :constellation_name, - :region_name, - :system_class, - :security, - :type_description, - :class_title, - :is_shattered, - :effect_name, - :effect_power, - :statics, - :wandering, - :triglavian_invasion_status, - :sun_type_id - ]) - - defp map_ui_kill({solar_system_id, kills}), - do: %{solar_system_id: solar_system_id, kills: kills} - - defp map_ui_kill(_kill), do: %{} - - defp map_ui_connection( - %{ - solar_system_source: solar_system_source, - solar_system_target: solar_system_target, - mass_status: mass_status, - time_status: time_status, - ship_size_type: ship_size_type, - locked: locked - } = _connection - ), - do: %{ - id: "#{solar_system_source}_#{solar_system_target}", - mass_status: mass_status, - time_status: time_status, - ship_size_type: ship_size_type, - locked: locked, - source: "#{solar_system_source}", - target: "#{solar_system_target}" - } - - defp map_ui_character(character), - do: - character - |> Map.take([ - :eve_id, - :name, - :online, - :corporation_id, - :corporation_name, - :corporation_ticker, - :alliance_id, - :alliance_name, - :alliance_ticker - ]) - |> Map.put_new(:ship, WandererApp.Character.get_ship(character)) - |> Map.put_new(:location, get_location(character)) - - defp map_ui_character_stat(character), - do: - character - |> Map.take([ - :eve_id, - :name, - :corporation_ticker, - :alliance_ticker - ]) - - defp get_location(character), - do: %{solar_system_id: character.solar_system_id, structure_id: character.structure_id} - - defp map_system( - %{ - solar_system_name: solar_system_name, - constellation_name: constellation_name, - region_name: region_name, - solar_system_id: solar_system_id, - class_title: class_title - } = _system - ), - do: %{ - label: solar_system_name, - value: solar_system_id, - constellation_name: constellation_name, - region_name: region_name, - class_title: class_title - } - - defp parse_signatures(signatures, character_eve_id, system_id), - do: - signatures - |> Enum.map(fn %{ - "eve_id" => eve_id, - "name" => name, - "kind" => kind, - "group" => group - } = signature -> - %{ - system_id: system_id, - eve_id: eve_id, - name: name, - description: Map.get(signature, "description"), - kind: kind, - group: group, - type: Map.get(signature, "type"), - character_eve_id: character_eve_id - } - end) - - defp _get_routes_settings(%{ - "path_type" => path_type, - "include_mass_crit" => include_mass_crit, - "include_eol" => include_eol, - "include_frig" => include_frig, - "include_cruise" => include_cruise, - "avoid_wormholes" => avoid_wormholes, - "avoid_pochven" => avoid_pochven, - "avoid_edencom" => avoid_edencom, - "avoid_triglavian" => avoid_triglavian, - "include_thera" => include_thera, - "avoid" => avoid - }), - do: %{ - path_type: path_type, - include_mass_crit: include_mass_crit, - include_eol: include_eol, - include_frig: include_frig, - include_cruise: include_cruise, - avoid_wormholes: avoid_wormholes, - avoid_pochven: avoid_pochven, - avoid_edencom: avoid_edencom, - avoid_triglavian: avoid_triglavian, - include_thera: include_thera, - avoid: avoid - } - - defp _get_routes_settings(_), do: %{} - - defp _add_characters([], _map_id, _track_character), do: :ok - - defp _add_characters([character | characters], map_id, track_character) do - map_id - |> WandererApp.Map.Server.add_character(character, track_character) - - _add_characters(characters, map_id, track_character) - end - - defp _remove_characters([], _map_id), do: :ok - - defp _remove_characters([character | characters], map_id) do - map_id - |> WandererApp.Map.Server.remove_character(character.id) - - _remove_characters(characters, map_id) - end - - defp _untrack_characters(characters, map_id) do - characters - |> Enum.each(fn character -> - WandererAppWeb.Presence.untrack(self(), map_id, character.id) - - WandererApp.Cache.put( - "#{inspect(self())}_map_#{map_id}:character_#{character.id}:tracked", - false - ) - - :ok = - Phoenix.PubSub.unsubscribe( - WandererApp.PubSub, - "character:#{character.eve_id}" - ) - end) - end - - defp _track_characters(_, _, false), do: :ok - - defp _track_characters([], _map_id, _is_track_character?), do: :ok - - defp _track_characters( - [character | characters], - map_id, - true - ) do - _track_character(character, map_id) - - _track_characters(characters, map_id, true) - end - - defp _track_character( - %{ - id: character_id, - eve_id: eve_id, - corporation_id: corporation_id, - alliance_id: alliance_id - }, - map_id - ) do - WandererAppWeb.Presence.track(self(), map_id, character_id, %{}) - - case WandererApp.Cache.lookup!( - "#{inspect(self())}_map_#{map_id}:character_#{character_id}:tracked", - false - ) do - true -> - :ok - - _ -> - :ok = - Phoenix.PubSub.subscribe( - WandererApp.PubSub, - "character:#{eve_id}" - ) - - :ok = - WandererApp.Cache.put( - "#{inspect(self())}_map_#{map_id}:character_#{character_id}:tracked", - true - ) - end - - case WandererApp.Cache.lookup( - "#{inspect(self())}_map_#{map_id}:corporation_#{corporation_id}:tracked", - false - ) do - {:ok, true} -> - :ok - - {:ok, false} -> - :ok = - Phoenix.PubSub.subscribe( - WandererApp.PubSub, - "corporation:#{corporation_id}" - ) - - :ok = - WandererApp.Cache.put( - "#{inspect(self())}_map_#{map_id}:corporation_#{corporation_id}:tracked", - true - ) - end - - case WandererApp.Cache.lookup( - "#{inspect(self())}_map_#{map_id}:alliance_#{alliance_id}:tracked", - false - ) do - {:ok, true} -> - :ok - - {:ok, false} -> - :ok = - Phoenix.PubSub.subscribe( - WandererApp.PubSub, - "alliance:#{alliance_id}" - ) - - :ok = - WandererApp.Cache.put( - "#{inspect(self())}_map_#{map_id}:alliance_#{alliance_id}:tracked", - true - ) - end - - :ok = WandererApp.Character.TrackerManager.start_tracking(character_id) - end - - defp push_map_event(socket, type, body), - do: - socket - |> push_event("map_event", %{ - type: type, - body: body - }) - - defp map_id(%{assigns: %{map_id: map_id}} = _socket), do: map_id end diff --git a/lib/wanderer_app_web/live/maps/map_live.html.heex b/lib/wanderer_app_web/live/maps/map_live.html.heex index 1dba07ce..07417ebe 100644 --- a/lib/wanderer_app_web/live/maps/map_live.html.heex +++ b/lib/wanderer_app_web/live/maps/map_live.html.heex @@ -104,20 +104,18 @@ id="characters-tracking-table" class="h-[400px] !overflow-y-auto" rows={characters} - row_click={fn character -> send(self(), "toggle_track_#{character.id}") end} + > <:col :let={character} label="Tracked"> -
- +
+ diff --git a/lib/wanderer_app_web/live/maps/maps_live.ex b/lib/wanderer_app_web/live/maps/maps_live.ex index 9c823fb6..a45feb39 100644 --- a/lib/wanderer_app_web/live/maps/maps_live.ex +++ b/lib/wanderer_app_web/live/maps/maps_live.ex @@ -8,11 +8,14 @@ defmodule WandererAppWeb.MapsLive do @pubsub_client Application.compile_env(:wanderer_app, :pubsub_client) @impl true - def mount(_params, %{"user_id" => user_id} = _session, socket) when not is_nil(user_id) do + def mount( + _params, + %{"user_id" => user_id} = _session, + %{assigns: %{current_user: current_user}} = socket + ) + when not is_nil(user_id) do {:ok, active_characters} = WandererApp.Api.Character.active_by_user(%{user_id: user_id}) - current_user = socket.assigns.current_user - user_characters = active_characters |> Enum.map(&map_character/1) @@ -601,16 +604,6 @@ defmodule WandererAppWeb.MapsLive do {added_acls, removed_acls} = map.acls |> Enum.map(& &1.id) |> _get_acls_diff(form["acls"]) - added_acls - |> Enum.each(fn acl_id -> - :telemetry.execute([:wanderer_app, :map, :acl, :add], %{count: 1}) - end) - - removed_acls - |> Enum.each(fn acl_id -> - :telemetry.execute([:wanderer_app, :map, :acl, :remove], %{count: 1}) - end) - Phoenix.PubSub.broadcast( WandererApp.PubSub, "maps:#{map.id}",