fix: fixed activity aggregation and new user tracking (#230)
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / Manual Approval (push) Blocked by required conditions
Build / 🛠 Build (1.17, 18.x, 27) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/arm64) (push) Blocked by required conditions
Build / merge (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions

This commit is contained in:
Dmitry Popov
2025-03-11 17:47:30 +01:00
parent 1590c848c9
commit 3937330ce4
11 changed files with 84 additions and 261 deletions
@@ -1,7 +1,5 @@
:global {
.p-datatable .p-datatable-thead > tr > th {
background-color: var(--surface-ground);
padding: 0.5rem;
text-align: center;
white-space: normal;
overflow: visible;
@@ -1,3 +1,3 @@
.trackFollowHeader {
background-color: var(--surface-ground);
}
background-color: #1e1e1e;
}
@@ -29,16 +29,14 @@ export const TrackAndFollow = ({ visible, onHide }: TrackAndFollowProps) => {
useEffect(() => {
if (trackingCharactersData) {
const newTrackedCharacters = trackingCharactersData
.filter(character => character.tracked)
.map(character => character.id);
const newTrackedCharacters = trackingCharactersData.filter(tc => tc.tracked).map(tc => tc.character.eve_id);
setTrackedCharacters(newTrackedCharacters);
const followedChar = trackingCharactersData.find(character => character.followed);
const followedChar = trackingCharactersData.find(tc => tc.followed);
if (followedChar?.id !== followedCharacter) {
setFollowedCharacter(followedChar?.id || null);
if (followedChar?.character?.eve_id !== followedCharacter) {
setFollowedCharacter(followedChar?.character?.eve_id || null);
}
}
}, [followedCharacter, trackingCharactersData]);
@@ -90,15 +88,15 @@ export const TrackAndFollow = ({ visible, onHide }: TrackAndFollowProps) => {
});
};
const rowTemplate = (character: TrackingCharacter) => {
const rowTemplate = (tc: TrackingCharacter) => {
return (
<TrackingCharacterWrapper
key={character.id}
character={character}
isTracked={trackedCharacters.includes(character.id)}
isFollowed={followedCharacter === character.id}
onTrackToggle={() => handleTrackToggle(character.id)}
onFollowToggle={() => handleFollowToggle(character.id)}
key={tc.character.eve_id}
character={tc.character}
isTracked={trackedCharacters.includes(tc.character.eve_id)}
isFollowed={followedCharacter === tc.character.eve_id}
onTrackToggle={() => handleTrackToggle(tc.character.eve_id)}
onFollowToggle={() => handleFollowToggle(tc.character.eve_id)}
/>
);
};
@@ -108,18 +106,11 @@ export const TrackAndFollow = ({ visible, onHide }: TrackAndFollowProps) => {
header={renderHeader()}
visible={visible}
onHide={onHide}
className="w-[500px] bg-surface-card text-text-color"
className="w-[500px] text-text-color"
contentClassName="!p-0"
>
<div className="w-full overflow-hidden">
<div
className={`
grid grid-cols-[80px_80px_1fr]
${classes.trackFollowHeader}
border-b border-surface-border
font-normal text-sm text-text-color
p-0.5 text-center
`}
>
<div className="grid grid-cols-[80px_80px_1fr] p-1 font-normal text-sm text-center bg-neutral-800">
<div>Track</div>
<div>Follow</div>
<div className="text-center">Character</div>
@@ -1,11 +1,10 @@
import { TrackingCharacter } from './types';
import { WdCheckbox } from '@/hooks/Mapper/components/ui-kit/WdCheckbox/WdCheckbox';
import WdRadioButton from '@/hooks/Mapper/components/ui-kit/WdRadioButton';
import { TooltipPosition, WdTooltipWrapper } from '../../../ui-kit';
import classes from './TrackingCharacterWrapper.module.scss';
import { CharacterCard, TooltipPosition, WdTooltipWrapper } from '../../../ui-kit';
import { CharacterTypeRaw } from '@/hooks/Mapper/types';
interface TrackingCharacterWrapperProps {
character: TrackingCharacter;
character: CharacterTypeRaw;
isTracked: boolean;
isFollowed: boolean;
onTrackToggle: () => void;
@@ -19,17 +18,11 @@ export const TrackingCharacterWrapper = ({
onTrackToggle,
onFollowToggle,
}: TrackingCharacterWrapperProps) => {
const trackCheckboxId = `track-${character.id}`;
const followRadioId = `follow-${character.id}`;
const trackCheckboxId = `track-${character.eve_id}`;
const followRadioId = `follow-${character.eve_id}`;
return (
<div
className={`
grid grid-cols-[80px_80px_1fr]
${classes.characterRow}
p-0.5 items-center transition-colors duration-200 min-h-8 hover:bg-surface-hover
`}
>
<div className="p-selectable-row grid grid-cols-[80px_80px_1fr] items-center min-h-8 hover:bg-neutral-800">
<div className="flex justify-center items-center p-0.5 text-center">
<WdTooltipWrapper content="Track this character on the map" position={TooltipPosition.top}>
<div className="flex justify-center items-center w-full">
@@ -46,25 +39,8 @@ export const TrackingCharacterWrapper = ({
</div>
</WdTooltipWrapper>
</div>
<div className="p-0.5 flex items-center justify-center">
<div className="flex items-center gap-3 w-full overflow-hidden min-h-8 justify-center">
<div className="w-8 h-8 rounded-full overflow-hidden flex-shrink-0">
<img src={character.portrait_url} alt={character.name} className="w-full h-full object-cover" />
</div>
<div className="flex items-center overflow-hidden flex-nowrap whitespace-nowrap">
<span
className={`
text-sm text-color-color whitespace-nowrap overflow-hidden text-ellipsis max-w-[150px]
`}
>
{character.name}
</span>
<span className="ml-2 text-text-color-secondary text-sm">[{character.corporation_ticker}]</span>
{character.alliance_ticker && (
<span className="ml-1 text-text-color-secondary text-sm">[{character.alliance_ticker}]</span>
)}
</div>
</div>
<div className="flex items-center justify-center">
<CharacterCard showShipName={false} showSystem={false} isOwn {...character} />
</div>
</div>
);
@@ -1,12 +1,9 @@
import { CharacterTypeRaw } from '@/hooks/Mapper/types';
/**
* Interface for a character that can be tracked and followed
*/
export interface TrackingCharacter {
id: string;
name: string;
corporation_ticker: string;
alliance_ticker?: string;
portrait_url: string;
character: CharacterTypeRaw;
tracked: boolean;
followed: boolean;
}
@@ -67,7 +67,7 @@
</div>
<div
:if={@mode == :blocks}
class="gap-4 grid grid-cols-1 lg:grid-cols-5 md:grid-cols-3 sm:grid-cols-2 mt-4"
class="gap-4 grid grid-cols-1 lg:grid-cols-4 md:grid-cols-3 sm:grid-cols-2 mt-4"
>
<.link patch={~p"/characters/authorize"}>
<div class="card card-side rounded-none h-full items-center hover:text-white bg-gradient-to-l from-stone-950 to-stone-900 transform transition duration-500">
@@ -103,56 +103,23 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
}
} = socket
) do
# Get all character settings to preserve followed state
{:ok, all_settings} = WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id)
# Get tracked characters
{:ok, map_characters} = WandererApp.Maps.get_tracked_map_characters(map_id, current_user)
user_character_eve_ids = map_characters |> Enum.map(& &1.eve_id)
{:ok, tracking_data} = build_tracking_data(map_id, current_user)
# Update socket assigns but don't affect followed state
socket =
socket
|> assign(user_characters: user_character_eve_ids)
|> assign(has_tracked_characters?: has_tracked_characters?(user_character_eve_ids))
# Get the map with ACLs for building tracking data
{:ok, map} = WandererApp.Api.Map.by_id(map_id)
map = Ash.load!(map, :acls)
# Get characters that have access to the map
{:ok, %{characters: characters_with_access}} =
WandererApp.Maps.load_characters(map, all_settings, current_user.id)
{:ok, latest_settings} = WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id)
# Create tracking data that preserves followed state
tracking_data =
characters_with_access
|> Enum.map(fn char ->
# Find existing settings to preserve followed state
# Use the latest settings to ensure we have the most up-to-date followed state
setting = Enum.find(latest_settings, &(&1.character_id == char.id))
# Keep the existing tracked and followed states
tracked = if setting, do: setting.tracked, else: false
followed = if setting, do: setting.followed, else: false
%{
id: char.eve_id,
name: char.name,
corporation_ticker: char.corporation_ticker,
alliance_ticker: Map.get(char, :alliance_ticker, ""),
portrait_url: EVEUtil.get_portrait_url(char.eve_id),
tracked: tracked,
followed: followed
}
end)
socket
|> assign(user_characters: user_character_eve_ids)
|> assign(has_tracked_characters?: has_tracked_characters?(user_character_eve_ids))
|> MapEventHandler.push_map_event(
"tracking_characters_data",
%{characters: tracking_data}
"init",
%{
user_characters: user_character_eve_ids,
reset: false
}
)
end
@@ -162,7 +129,7 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
# UI Event Handlers
def handle_ui_event(
"toggle_track",
%{"character-id" => clicked_char_id},
%{"character-id" => character_eve_id},
%{
assigns: %{
map_id: map_id,
@@ -185,7 +152,7 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
# Find the character we're toggling
with {:ok, character} <-
WandererApp.Character.find_character_by_eve_id(current_user, clicked_char_id),
WandererApp.Character.find_character_by_eve_id(current_user, character_eve_id),
{:ok, updated_settings} <-
toggle_character_tracking(character, map_id, only_tracked_characters) do
# Get the map with ACLs
@@ -212,45 +179,7 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
end
end
# Get updated settings after potentially restoring followed state
{:ok, new_all_settings} = WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id)
# Create tracking data for characters with access to the map
tracking_data =
characters_with_access
|> Enum.map(fn char ->
# For the character being toggled, use the updated settings
setting =
if "#{char.eve_id}" == "#{clicked_char_id}" do
updated_settings
else
# For other characters, use the updated settings
current_setting = Enum.find(new_all_settings, &(&1.character_id == char.id))
# If this was the previously followed character, make sure it's still followed
if followed_character_id && followed_character_id == char.id &&
current_setting && !current_setting.followed do
# This character was previously followed but is no longer followed
# Restore the followed state
%{current_setting | followed: true}
else
current_setting
end
end
tracked = if setting, do: setting.tracked, else: false
followed = if setting, do: setting.followed, else: false
%{
id: char.eve_id,
name: char.name,
corporation_ticker: char.corporation_ticker,
alliance_ticker: Map.get(char, :alliance_ticker, ""),
portrait_url: EVEUtil.get_portrait_url(char.eve_id),
tracked: tracked,
followed: followed
}
end)
{:ok, tracking_data} = build_tracking_data(map_id, current_user)
{:noreply,
socket
@@ -270,7 +199,7 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
%{assigns: %{map_id: map_id, current_user: current_user}} = socket
) do
# Create tracking data for characters with access to the map
{:ok, tracking_data} = get_tracking_data(map_id, current_user)
{:ok, tracking_data} = build_tracking_data(map_id, current_user)
{:noreply,
socket
@@ -304,14 +233,10 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
toggle_character_follow(map_id, clicked_char, is_already_followed) do
# Get the state after the toggle_character_follow operation
{:ok, all_settings_after} = WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id)
_followed_after = all_settings_after |> Enum.find(& &1.followed)
# Build tracking data
{:ok, tracking_data} = build_tracking_data(map_id, current_user)
# Get the followed character in the tracking data
_followed_in_tracking = tracking_data |> Enum.find(& &1.followed)
{:noreply,
socket
|> MapEventHandler.push_map_event("tracking_characters_data", %{characters: tracking_data})}
@@ -355,66 +280,9 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
def handle_ui_event("hide_activity", _, socket),
do: {:noreply, socket |> assign(show_activity?: false)}
def handle_ui_event("add_character", _, socket) do
{:noreply,
socket
|> MapEventHandler.push_map_event("show_tracking", %{})}
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."
)}
end
def handle_ui_event(event, body, socket),
do: MapCoreEventHandler.handle_ui_event(event, body, socket)
defp get_tracking_data(map_id, current_user) do
# Get character settings for this map
{:ok, character_settings} = WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id)
# Get the map with ACLs
{:ok, map} = WandererApp.Api.Map.by_id(map_id)
map = Ash.load!(map, :acls)
# Get all user characters
{:ok, _all_user_characters} =
WandererApp.Api.Character.active_by_user(%{user_id: current_user.id})
# Get characters that have access to the map using load_characters
# This will include all characters with access, even if they're not tracked
{:ok, %{characters: characters_with_access}} =
WandererApp.Maps.load_characters(map, character_settings, current_user.id)
# Create tracking data for characters with access to the map
{:ok,
characters_with_access
|> Enum.map(fn char ->
# Find settings for this character if they exist
setting = Enum.find(character_settings, &(&1.character_id == char.id))
tracked = if setting, do: setting.tracked, else: false
followed = if setting, do: setting.followed, else: false
%{
id: char.eve_id,
name: char.name,
corporation_ticker: char.corporation_ticker,
alliance_ticker: Map.get(char, :alliance_ticker, ""),
portrait_url: EVEUtil.get_portrait_url(char.eve_id),
tracked: tracked,
followed: followed
}
end)}
end
def has_tracked_characters?([]), do: false
def has_tracked_characters?(_user_characters), do: true
@@ -582,20 +450,15 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
socket = init_tracking_state(socket, current_user)
needs_tracking_setup =
needs_tracking_setup?(characters_with_access, character_settings, user_permissions)
socket =
socket
|> assign(:needs_tracking_setup, needs_tracking_setup)
|> then(fn socket ->
if needs_tracking_setup do
socket
else
socket
end
end)
needs_tracking_setup?(
socket.assigns.only_tracked_characters,
characters_with_access,
character_settings,
user_permissions
)
socket
|> assign(:needs_tracking_setup, needs_tracking_setup)
end
defp get_map_with_acls(map_id) do
@@ -619,7 +482,19 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
)
end
def needs_tracking_setup?(characters, character_settings, user_permissions) do
def needs_tracking_setup?(
only_tracked_characters,
characters,
character_settings,
user_permissions
) do
tracked_count =
characters
|> Enum.count(fn char ->
setting = Enum.find(character_settings, &(&1.character_id == char.id))
setting && setting.tracked
end)
untracked_count =
characters
|> Enum.count(fn char ->
@@ -627,7 +502,8 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
setting == nil || !setting.tracked
end)
untracked_count > 0 && user_permissions.track_character
user_permissions.track_character &&
((untracked_count > 0 && only_tracked_characters) || tracked_count == 0)
end
@doc """
@@ -839,11 +715,7 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
followed = if setting, do: setting.followed, else: false
%{
id: char.eve_id,
name: char.name,
corporation_ticker: char.corporation_ticker,
alliance_ticker: Map.get(char, :alliance_ticker, ""),
portrait_url: EVEUtil.get_portrait_url(char.eve_id),
character: char |> MapEventHandler.map_ui_character_stat(),
tracked: tracked,
followed: followed
}
@@ -854,7 +726,7 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
end
# Helper function to toggle character tracking
defp toggle_character_tracking(character, map_id, _only_tracked_characters) do
defp toggle_character_tracking(character, map_id, only_tracked_characters) do
case WandererApp.MapCharacterSettingsRepo.get_by_map(map_id, character.id) do
{:ok, existing_settings} ->
if existing_settings.tracked do
@@ -862,6 +734,13 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
{:ok, updated_settings} =
WandererApp.MapCharacterSettingsRepo.untrack(existing_settings)
: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
# If the character was followed, we need to unfollow it too
# But we should NOT unfollow other characters
if existing_settings.followed do
@@ -877,6 +756,10 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
{:ok, updated_settings} =
WandererApp.MapCharacterSettingsRepo.track(existing_settings)
:ok = track_characters([character], map_id, true)
:ok = add_characters([character], map_id, true)
Process.send_after(self(), %{event: :refresh_user_characters}, 10)
{:ok, updated_settings}
end
@@ -174,22 +174,6 @@ defmodule WandererAppWeb.MapCoreEventHandler do
{:noreply, socket}
end
def handle_ui_event("toggle_track", %{"character-id" => character_id}, socket),
do:
MapCharactersEventHandler.handle_ui_event(
"toggle_track",
%{"character-id" => character_id},
socket
)
def handle_ui_event("toggle_follow", %{"character-id" => character_id}, socket),
do:
MapCharactersEventHandler.handle_ui_event(
"toggle_follow",
%{"character-id" => character_id},
socket
)
def handle_ui_event(
"get_user_settings",
_,
@@ -25,7 +25,6 @@ defmodule WandererAppWeb.MapEventHandler do
]
@map_characters_ui_events [
"add_character",
"toggle_track",
"toggle_follow",
"hide_tracking",
@@ -84,9 +83,7 @@ defmodule WandererAppWeb.MapEventHandler do
@map_activity_ui_events [
"show_activity",
"hide_activity",
"toggle_follow",
"toggle_track"
"hide_activity"
]
@map_routes_events [
+7 -10
View File
@@ -76,16 +76,13 @@ defmodule WandererAppWeb.MapLive do
def handle_info(:not_all_characters_tracked, %{assigns: %{map_slug: map_slug}} = socket),
do:
WandererAppWeb.MapEventHandler.handle_ui_event(
"add_character",
nil,
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}")
)
{:noreply,
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}")}
@impl true
def handle_info(info, socket),
@@ -1,7 +1,7 @@
<div class="grid grid-flow-row gap-2 p-3 h-full w-full pl-20">
<main class="w-full rounded-lg shadow col-span-2 lg:col-span-1 overflow-auto p-3">
<%= if @maps != [] do %>
<div class="gap-4 grid grid-cols-2 lg:grid-cols-5 md:grid-cols-4 sm:grid-cols-3 ">
<div class="gap-4 grid grid-cols-2 lg:grid-cols-5 md:grid-cols-3 sm:grid-cols-3 ">
<.link
:if={not @restrict_maps_creation?}
class="card h-[250px] rounded-none bg-gradient-to-l from-stone-950 to-stone-900 hover:text-white transform transition duration-500"