diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a0bdbad..10905feb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,79 @@ +## [v1.43.6](https://github.com/wanderer-industries/wanderer/compare/v1.43.5...v1.43.6) (2025-01-22) + + + + +### Bug Fixes: + +* Widgets: Fix widgets not visible on map + +## [v1.43.5](https://github.com/wanderer-industries/wanderer/compare/v1.43.4...v1.43.5) (2025-01-22) + + + + +### Bug Fixes: + +* Audit: Fix signature added/removed system name + +## [v1.43.4](https://github.com/wanderer-industries/wanderer/compare/v1.43.3...v1.43.4) (2025-01-21) + + + + +### Bug Fixes: + +* improve structure widget styling (#127) + +## [v1.43.3](https://github.com/wanderer-industries/wanderer/compare/v1.43.2...v1.43.3) (2025-01-21) + + + + +## [v1.43.2](https://github.com/wanderer-industries/wanderer/compare/v1.43.1...v1.43.2) (2025-01-21) + + + + +### Bug Fixes: + +* prevent constraint error for follow/toggle (#132) + +## [v1.43.1](https://github.com/wanderer-industries/wanderer/compare/v1.43.0...v1.43.1) (2025-01-20) + + + + +## [v1.43.0](https://github.com/wanderer-industries/wanderer/compare/v1.42.5...v1.43.0) (2025-01-20) + + + + +### Features: + +* add news post for structures widget (#131) + +## [v1.42.5](https://github.com/wanderer-industries/wanderer/compare/v1.42.4...v1.42.5) (2025-01-20) + + + + +### Bug Fixes: + +* Map: Fix link signatures on splash. Fix deleting connection on locked system remove. + +## [v1.42.4](https://github.com/wanderer-industries/wanderer/compare/v1.42.3...v1.42.4) (2025-01-20) + + + + +### Bug Fixes: + +* Fix system statics list (required EVE DB data update). Add system name to signature added/removed audit log + ## [v1.42.3](https://github.com/wanderer-industries/wanderer/compare/v1.42.2...v1.42.3) (2025-01-17) diff --git a/assets/js/hooks/Mapper/components/map/Map.tsx b/assets/js/hooks/Mapper/components/map/Map.tsx index e0302649..49e00766 100644 --- a/assets/js/hooks/Mapper/components/map/Map.tsx +++ b/assets/js/hooks/Mapper/components/map/Map.tsx @@ -2,6 +2,7 @@ import { ForwardedRef, forwardRef, MouseEvent, useCallback, useEffect, useMemo } import ReactFlow, { Background, Edge, + EdgeChange, MiniMap, Node, NodeChange, @@ -83,6 +84,7 @@ interface MapCompProps { onCommand: OutCommandHandler; onSelectionChange: OnMapSelectionChange; onManualDelete(systems: string[]): void; + canRemoveConnection?(connectionId: string): boolean; onConnectionInfoClick?(e: SolarSystemConnection): void; onAddSystem?: OnMapAddSystemCallback; onSelectionContextMenu?: NodeSelectionMouseHandler; @@ -112,8 +114,9 @@ const MapComp = ({ isSoftBackground, theme, onAddSystem, + canRemoveConnection, }: MapCompProps) => { - const { getNode, getNodes } = useReactFlow(); + const { getEdge, getNode, getNodes } = useReactFlow(); const [nodes, , onNodesChange] = useNodesState>(initialNodes); const [edges, , onEdgesChange] = useEdgesState>(initialEdges); @@ -222,6 +225,40 @@ const MapComp = ({ [getNode, getNodes, onManualDelete, onNodesChange], ); + const handleEdgesChange = useCallback( + (changes: EdgeChange[]) => { + const nextChanges = changes.reduce((acc, change) => { + if (change.type !== 'remove') { + return [...acc, change]; + } + + if (canRemoveConnection?.(change.id)) { + return [...acc, change]; + } + + const edge = getEdge(change.id); + if (!edge) { + return [...acc, change]; + } + + const sourceNode = getNode(edge.source); + const targetNode = getNode(edge.target); + if (!sourceNode || !targetNode) { + return [...acc, change]; + } + + if (sourceNode.data.locked || targetNode.data.locked) { + return acc; + } + + return [...acc, change]; + }, [] as EdgeChange[]); + + onEdgesChange(nextChanges); + }, + [getEdge, getNode, onEdgesChange], + ); + useEffect(() => { update(x => ({ ...x, @@ -237,7 +274,7 @@ const MapComp = ({ nodes={nodes} edges={edges} onNodesChange={handleNodesChange} - onEdgesChange={onEdgesChange} + onEdgesChange={handleEdgesChange} onConnect={onConnect} // TODO we need save into session all of this // and on any action do either diff --git a/assets/js/hooks/Mapper/components/map/hooks/useMapHandlers.ts b/assets/js/hooks/Mapper/components/map/hooks/useMapHandlers.ts index dbb8eb60..849596e8 100644 --- a/assets/js/hooks/Mapper/components/map/hooks/useMapHandlers.ts +++ b/assets/js/hooks/Mapper/components/map/hooks/useMapHandlers.ts @@ -70,7 +70,7 @@ export const useMapHandlers = (ref: ForwardedRef, onSelectionChange setTimeout(() => addConnections(data as CommandAddConnections), 100); break; case Commands.removeConnections: - removeConnections(data as CommandRemoveConnections); + setTimeout(() => removeConnections(data as CommandRemoveConnections), 100); break; case Commands.charactersUpdated: charactersUpdated(data as CommandCharactersUpdated); diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemStructures/SystemStructures.tsx b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemStructures/SystemStructures.tsx index 13a22cb6..3aba636f 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemStructures/SystemStructures.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemStructures/SystemStructures.tsx @@ -70,6 +70,11 @@ export const SystemStructures: React.FC = () => { tr > td { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} + +.Tooltip { + white-space: pre-line; + line-height: 1.2rem; +} diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemStructures/SystemStructuresContent/SystemStructuresContent.tsx b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemStructures/SystemStructuresContent/SystemStructuresContent.tsx index 4ce0a1eb..bc62c8b6 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemStructures/SystemStructuresContent/SystemStructuresContent.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemStructures/SystemStructuresContent/SystemStructuresContent.tsx @@ -63,6 +63,7 @@ export const SystemStructuresContent: React.FC = ( size="small" sortMode="single" rowHover + style={{ tableLayout: 'fixed', width: '100%' }} onRowClick={handleRowClick} onRowDoubleClick={handleRowDoubleClick} rowClassName={rowData => { @@ -74,11 +75,56 @@ export const SystemStructuresContent: React.FC = ( ); }} > - - - - - + + + + + ( = ( }} /> )} - style={{ width: '40px', textAlign: 'center' }} + style={{ + width: '40px', + textAlign: 'center', + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + }} /> diff --git a/assets/js/hooks/Mapper/components/mapWrapper/MapWrapper.tsx b/assets/js/hooks/Mapper/components/mapWrapper/MapWrapper.tsx index ceb076a7..bce3f1dd 100644 --- a/assets/js/hooks/Mapper/components/mapWrapper/MapWrapper.tsx +++ b/assets/js/hooks/Mapper/components/mapWrapper/MapWrapper.tsx @@ -14,9 +14,10 @@ import classes from './MapWrapper.module.scss'; import { Connections } from '@/hooks/Mapper/components/mapRootContent/components/Connections'; import { ContextMenuSystemMultiple, useContextMenuSystemMultipleHandlers } from '../contexts/ContextMenuSystemMultiple'; import { getSystemById } from '@/hooks/Mapper/helpers'; +import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts'; import { Node, XYPosition } from 'reactflow'; -import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts'; +import { useCommandsSystems } from '@/hooks/Mapper/mapRootProvider/hooks/api'; import { emitMapEvent, useMapEventListener } from '@/hooks/Mapper/events'; import { STORED_INTERFACE_DEFAULT_VALUES } from '@/hooks/Mapper/mapRootProvider/MapRootProvider'; @@ -32,7 +33,7 @@ export const MapWrapper = () => { const { update, outCommand, - data: { selectedConnections, selectedSystems, hubs, systems }, + data: { selectedConnections, selectedSystems, hubs, systems, connections, linkSignatureToSystem }, interfaceSettings: { isShowMenu, isShowMinimap = STORED_INTERFACE_DEFAULT_VALUES.isShowMinimap, @@ -46,25 +47,19 @@ export const MapWrapper = () => { const { deleteSystems } = useDeleteSystems(); const { mapRef, runCommand } = useCommonMapEventProcessor(); + const { updateLinkSignatureToSystem } = useCommandsSystems(); const { open, ...systemContextProps } = useContextMenuSystemHandlers({ systems, hubs, outCommand }); const { handleSystemMultipleContext, ...systemMultipleCtxProps } = useContextMenuSystemMultipleHandlers(); const [openSettings, setOpenSettings] = useState(null); - const [openLinkSignatures, setOpenLinkSignatures] = useState(null); const [openCustomLabel, setOpenCustomLabel] = useState(null); const [openAddSystem, setOpenAddSystem] = useState(null); const [selectedConnection, setSelectedConnection] = useState(null); - const ref = useRef({ selectedConnections, selectedSystems, systemContextProps, systems, deleteSystems }); - ref.current = { selectedConnections, selectedSystems, systemContextProps, systems, deleteSystems }; + const ref = useRef({ selectedConnections, selectedSystems, systemContextProps, systems, connections, deleteSystems }); + ref.current = { selectedConnections, selectedSystems, systemContextProps, systems, connections, deleteSystems }; useMapEventListener(event => { - switch (event.name) { - case Commands.linkSignatureToSystem: - setOpenLinkSignatures(event.data); - return true; - } - runCommand(event); }); @@ -130,6 +125,11 @@ export const MapWrapper = () => { setOpenAddSystem(coordinates); }, []); + const canRemoveConnection = useCallback((connectionId: string) => { + const { connections } = ref.current; + return !connections.some(x => x.id === connectionId); + }, []); + const handleSubmitAddSystem: SearchOnSubmitCallback = useCallback( async item => { if (ref.current.systems.some(x => x.system_static_info.solar_system_id === item.value)) { @@ -166,6 +166,7 @@ export const MapWrapper = () => { isSoftBackground={isSoftBackground} theme={theme} onAddSystem={onAddSystem} + canRemoveConnection={canRemoveConnection} /> {openSettings != null && ( @@ -176,8 +177,8 @@ export const MapWrapper = () => { setOpenCustomLabel(null)} /> )} - {openLinkSignatures != null && ( - setOpenLinkSignatures(null)} /> + {linkSignatureToSystem != null && ( + updateLinkSignatureToSystem(null)} /> )} = ({ windows: initialWi next.position.y = container.clientHeight - next.size.height - SNAP_GAP; } + if (next.position.y < 0) { + next.position.y = 0; + } + return next; }); }); diff --git a/assets/js/hooks/Mapper/mapRootProvider/MapRootProvider.tsx b/assets/js/hooks/Mapper/mapRootProvider/MapRootProvider.tsx index 1071d5f8..a176673a 100644 --- a/assets/js/hooks/Mapper/mapRootProvider/MapRootProvider.tsx +++ b/assets/js/hooks/Mapper/mapRootProvider/MapRootProvider.tsx @@ -10,10 +10,12 @@ import { useStoreWidgets, WindowStoreInfo, } from '@/hooks/Mapper/mapRootProvider/hooks/useStoreWidgets.ts'; +import { CommandLinkSignatureToSystem } from '@/hooks/Mapper/types'; export type MapRootData = MapUnionTypes & { selectedSystems: string[]; selectedConnections: Pick[]; + linkSignatureToSystem: CommandLinkSignatureToSystem | null; }; const INITIAL_DATA: MapRootData = { @@ -34,6 +36,7 @@ const INITIAL_DATA: MapRootData = { selectedConnections: [], userPermissions: {}, options: {}, + linkSignatureToSystem: null, }; export enum InterfaceStoredSettingsProps { diff --git a/assets/js/hooks/Mapper/mapRootProvider/hooks/api/useCommandsSystems.ts b/assets/js/hooks/Mapper/mapRootProvider/hooks/api/useCommandsSystems.ts index a04bc92f..125df212 100644 --- a/assets/js/hooks/Mapper/mapRootProvider/hooks/api/useCommandsSystems.ts +++ b/assets/js/hooks/Mapper/mapRootProvider/hooks/api/useCommandsSystems.ts @@ -1,6 +1,11 @@ import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; import { useCallback, useRef } from 'react'; -import { CommandAddSystems, CommandRemoveSystems, CommandUpdateSystems } from '@/hooks/Mapper/types'; +import { + CommandAddSystems, + CommandRemoveSystems, + CommandUpdateSystems, + CommandLinkSignatureToSystem, +} from '@/hooks/Mapper/types'; import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts'; import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts'; import { emitMapEvent } from '@/hooks/Mapper/events'; @@ -74,5 +79,10 @@ export const useCommandsSystems = () => { [outCommand], ); - return { addSystems, removeSystems, updateSystems, updateSystemSignatures }; + const updateLinkSignatureToSystem = useCallback(async (command: CommandLinkSignatureToSystem) => { + const { update } = ref.current; + update({ linkSignatureToSystem: command }, true); + }, []); + + return { addSystems, removeSystems, updateSystems, updateSystemSignatures, updateLinkSignatureToSystem }; }; diff --git a/assets/js/hooks/Mapper/mapRootProvider/hooks/useMapRootHandlers.ts b/assets/js/hooks/Mapper/mapRootProvider/hooks/useMapRootHandlers.ts index b53281b6..7ce3c325 100644 --- a/assets/js/hooks/Mapper/mapRootProvider/hooks/useMapRootHandlers.ts +++ b/assets/js/hooks/Mapper/mapRootProvider/hooks/useMapRootHandlers.ts @@ -32,7 +32,8 @@ import { emitMapEvent } from '@/hooks/Mapper/events'; export const useMapRootHandlers = (ref: ForwardedRef) => { const mapInit = useMapInit(); - const { addSystems, removeSystems, updateSystems, updateSystemSignatures } = useCommandsSystems(); + const { addSystems, removeSystems, updateSystems, updateSystemSignatures, updateLinkSignatureToSystem } = + useCommandsSystems(); const { addConnections, removeConnections, updateConnection } = useCommandsConnections(); const { charactersUpdated, characterAdded, characterRemoved, characterUpdated, presentCharacters } = useCommandsCharacters(); @@ -93,7 +94,9 @@ export const useMapRootHandlers = (ref: ForwardedRef) => { break; case Commands.linkSignatureToSystem: // USED - // do nothing here + setTimeout(() => { + updateLinkSignatureToSystem(data as CommandLinkSignatureToSystem); + }, 200); break; case Commands.centerSystem: // USED diff --git a/assets/static/images/news/01-20-structure-widget/cover.png b/assets/static/images/news/01-20-structure-widget/cover.png new file mode 100755 index 00000000..c23d6e85 Binary files /dev/null and b/assets/static/images/news/01-20-structure-widget/cover.png differ diff --git a/assets/static/images/news/01-20-structure-widget/enable-widget.png b/assets/static/images/news/01-20-structure-widget/enable-widget.png new file mode 100755 index 00000000..7fad5fa2 Binary files /dev/null and b/assets/static/images/news/01-20-structure-widget/enable-widget.png differ diff --git a/lib/wanderer_app/map/server/map_server_systems_impl.ex b/lib/wanderer_app/map/server/map_server_systems_impl.ex index 70283a36..e1cc2a1c 100644 --- a/lib/wanderer_app/map/server/map_server_systems_impl.ex +++ b/lib/wanderer_app/map/server/map_server_systems_impl.ex @@ -206,16 +206,24 @@ defmodule WandererApp.Map.Server.SystemsImpl do user_id, character_id ) do - removed_system_ids = + filtered_ids = removed_ids |> Enum.map(fn solar_system_id -> WandererApp.Map.find_system_by_location(map_id, %{solar_system_id: solar_system_id}) end) - |> Enum.filter(fn system -> not is_nil(system) end) - |> Enum.map(& &1.id) + |> Enum.filter(fn system -> not is_nil(system) && not system.locked end) + |> Enum.map(&{&1.solar_system_id, &1.id}) + + solar_system_ids_to_remove = + filtered_ids + |> Enum.map(fn {solar_system_id, _} -> solar_system_id end) + + system_ids_to_remove = + filtered_ids + |> Enum.map(fn {_, system_id} -> system_id end) connections_to_remove = - removed_ids + solar_system_ids_to_remove |> Enum.map(fn solar_system_id -> WandererApp.Map.find_connections(map_id, solar_system_id) end) @@ -223,9 +231,9 @@ defmodule WandererApp.Map.Server.SystemsImpl do |> Enum.uniq_by(& &1.id) :ok = WandererApp.Map.remove_connections(map_id, connections_to_remove) - :ok = WandererApp.Map.remove_systems(map_id, removed_ids) + :ok = WandererApp.Map.remove_systems(map_id, solar_system_ids_to_remove) - removed_ids + solar_system_ids_to_remove |> Enum.each(fn solar_system_id -> map_id |> WandererApp.MapSystemRepo.remove_from_map(solar_system_id) @@ -245,7 +253,7 @@ defmodule WandererApp.Map.Server.SystemsImpl do WandererApp.MapConnectionRepo.destroy(map_id, connection) end) - removed_ids + solar_system_ids_to_remove |> Enum.map(fn solar_system_id -> WandererApp.Api.MapSystemSignature.by_linked_system_id!(solar_system_id) end) @@ -259,7 +267,7 @@ defmodule WandererApp.Map.Server.SystemsImpl do end) linked_system_ids = - removed_system_ids + system_ids_to_remove |> Enum.map(fn system_id -> WandererApp.Api.MapSystemSignature.by_system_id!(system_id) |> Enum.filter(fn s -> not is_nil(s.linked_system_id) end) @@ -276,10 +284,10 @@ defmodule WandererApp.Map.Server.SystemsImpl do }) end) - @ddrt.delete(removed_ids, rtree_name) + @ddrt.delete(solar_system_ids_to_remove, rtree_name) Impl.broadcast!(map_id, :remove_connections, connections_to_remove) - Impl.broadcast!(map_id, :systems_removed, removed_ids) + Impl.broadcast!(map_id, :systems_removed, solar_system_ids_to_remove) case not is_nil(user_id) do true -> @@ -288,12 +296,12 @@ defmodule WandererApp.Map.Server.SystemsImpl do character_id: character_id, user_id: user_id, map_id: map_id, - solar_system_ids: removed_ids + solar_system_ids: solar_system_ids_to_remove }) :telemetry.execute( [:wanderer_app, :map, :systems, :remove], - %{count: removed_ids |> Enum.count()} + %{count: solar_system_ids_to_remove |> Enum.count()} ) :ok diff --git a/lib/wanderer_app_web/components/user_activity.ex b/lib/wanderer_app_web/components/user_activity.ex index 05cb8e6e..043cc272 100644 --- a/lib/wanderer_app_web/components/user_activity.ex +++ b/lib/wanderer_app_web/components/user_activity.ex @@ -162,40 +162,40 @@ defmodule WandererAppWeb.UserActivity do "solar_system_id" => solar_system_id, "value" => value }) do - system_name = _get_system_name(solar_system_id) + system_name = get_system_name(solar_system_id) try do %{"customLabel" => customLabel, "labels" => labels} = Jason.decode!(value) - "#{system_name} labels - #{inspect(labels)}, customLabel - #{customLabel}" + "#{system_name}: labels - #{inspect(labels)}, customLabel - #{customLabel}" rescue _ -> - "#{system_name} labels - #{inspect(value)}" + "#{system_name}: labels - #{inspect(value)}" end end defp get_event_data(:system_added, %{ "solar_system_id" => solar_system_id }), - do: _get_system_name(solar_system_id) + do: get_system_name(solar_system_id) defp get_event_data(:hub_added, %{ "solar_system_id" => solar_system_id }), - do: _get_system_name(solar_system_id) + do: get_system_name(solar_system_id) defp get_event_data(:hub_removed, %{ "solar_system_id" => solar_system_id }), - do: _get_system_name(solar_system_id) + do: get_system_name(solar_system_id) defp get_event_data(:system_updated, %{ "key" => key, "solar_system_id" => solar_system_id, "value" => value }) do - system_name = _get_system_name(solar_system_id) - "#{system_name} #{key} - #{inspect(value)}" + system_name = get_system_name(solar_system_id) + "#{system_name}: #{key} - #{inspect(value)}" end defp get_event_data(:systems_removed, %{ @@ -203,29 +203,28 @@ defmodule WandererAppWeb.UserActivity do }), do: solar_system_ids - |> Enum.map(&_get_system_name/1) + |> Enum.map(&get_system_name/1) |> Enum.join(", ") - defp get_event_data(:signatures_added, %{ + defp get_event_data(signatures_event, %{ + "solar_system_id" => solar_system_id, "signatures" => signatures - }), - do: - signatures - |> Enum.join(", ") + }) + when signatures_event in [:signatures_added, :signatures_removed], + do: "#{get_system_name(solar_system_id)}: #{signatures |> Enum.join(", ")}" - defp get_event_data(:signatures_removed, %{ + defp get_event_data(signatures_event, %{ "signatures" => signatures - }), - do: - signatures - |> Enum.join(", ") + }) + when signatures_event in [:signatures_added, :signatures_removed], + do: signatures |> Enum.join(", ") defp get_event_data(:map_connection_added, %{ "solar_system_source_id" => solar_system_source_id, "solar_system_target_id" => solar_system_target_id }) do - source_system_name = _get_system_name(solar_system_source_id) - target_system_name = _get_system_name(solar_system_target_id) + source_system_name = get_system_name(solar_system_source_id) + target_system_name = get_system_name(solar_system_target_id) "[#{source_system_name}:#{target_system_name}]" end @@ -233,8 +232,8 @@ defmodule WandererAppWeb.UserActivity do "solar_system_source_id" => solar_system_source_id, "solar_system_target_id" => solar_system_target_id }) do - source_system_name = _get_system_name(solar_system_source_id) - target_system_name = _get_system_name(solar_system_target_id) + source_system_name = get_system_name(solar_system_source_id) + target_system_name = get_system_name(solar_system_target_id) "[#{source_system_name}:#{target_system_name}]" end @@ -244,14 +243,14 @@ defmodule WandererAppWeb.UserActivity do "solar_system_target_id" => solar_system_target_id, "value" => value }) do - source_system_name = _get_system_name(solar_system_source_id) - target_system_name = _get_system_name(solar_system_target_id) + source_system_name = get_system_name(solar_system_source_id) + target_system_name = get_system_name(solar_system_target_id) "[#{source_system_name}:#{target_system_name}] #{key} - #{inspect(value)}" end defp get_event_data(_name, data), do: Jason.encode!(data) - defp _get_system_name(solar_system_id) do + defp get_system_name(solar_system_id) do case WandererApp.CachedInfo.get_system_static_info(solar_system_id) do {:ok, nil} -> solar_system_id 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 index 065cda66..7ad6e84a 100644 --- 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 @@ -213,7 +213,7 @@ defmodule WandererAppWeb.MapCharactersEventHandler do s.character_id in user_char_ids end) - existing = Enum.find(my_settings, &(&1.character_id == clicked_char_id)) + existing = Enum.find(all_settings, &(&1.character_id == clicked_char_id)) {:ok, target_setting} = if not is_nil(existing) do 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 index f2fa2e90..c886d577 100644 --- 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 @@ -270,6 +270,8 @@ defmodule WandererAppWeb.MapCoreEventHandler do current_user.characters |> Enum.map(& &1.id) ) + {:ok, map_user_settings} = WandererApp.MapUserSettingsRepo.get(map_id, current_user.id) + {:ok, character_settings} = case WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id) do {:ok, settings} -> {:ok, settings} @@ -302,6 +304,7 @@ defmodule WandererAppWeb.MapCoreEventHandler do socket |> assign( map_id: map_id, + map_user_settings: map_user_settings, page_title: map_name, user_permissions: user_permissions, tracked_character_ids: tracked_character_ids, @@ -334,7 +337,6 @@ defmodule WandererAppWeb.MapCoreEventHandler do } = 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} <- WandererApp.Maps.get_tracked_map_characters(map_id, current_user), {:ok, characters_limit} <- map_id |> WandererApp.Map.get_characters_limit(), @@ -414,7 +416,6 @@ defmodule WandererAppWeb.MapCoreEventHandler do 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 @@ -437,7 +438,6 @@ defmodule WandererAppWeb.MapCoreEventHandler do socket, %{ map_id: map_id, - map_user_settings: map_user_settings, user_characters: user_character_eve_ids, initial_data: initial_data, events: events @@ -468,7 +468,6 @@ defmodule WandererAppWeb.MapCoreEventHandler do socket |> assign( map_loaded?: true, - map_user_settings: map_user_settings, user_characters: user_character_eve_ids, has_tracked_characters?: has_tracked_characters? ) 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 index 3ce4ca8a..63232faa 100644 --- 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 @@ -130,7 +130,7 @@ defmodule WandererAppWeb.MapSignaturesEventHandler do 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_source_id: system.solar_system_id, solar_system_target_id: s.linked_system_id }) end @@ -180,6 +180,7 @@ defmodule WandererAppWeb.MapSignaturesEventHandler do character_id: first_tracked_character.id, user_id: current_user.id, map_id: map_id, + solar_system_id: system.solar_system_id, signatures: added_signatures_eve_ids }) end @@ -190,6 +191,7 @@ defmodule WandererAppWeb.MapSignaturesEventHandler do character_id: first_tracked_character.id, user_id: current_user.id, map_id: map_id, + solar_system_id: system.solar_system_id, signatures: removed_signatures_eve_ids }) 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 index 0b000b9e..971a605e 100644 --- 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 @@ -21,57 +21,63 @@ defmodule WandererAppWeb.MapSystemsEventHandler do |> 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_id: map_id, map_user_settings: map_user_settings}} = socket - ) do + %{ + event: :maybe_select_system, + payload: %{ + character_id: character_id, + solar_system_id: solar_system_id + } + }, + %{ + 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_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_select_on_spash = - map_user_settings - |> WandererApp.MapUserSettingsRepo.to_form_data!() - |> WandererApp.MapUserSettingsRepo.get_boolean_setting("select_on_spash") + is_followed = + case WandererApp.MapCharacterSettingsRepo.get_by_map(map_id, character_id) do + {:ok, setting} -> setting.followed == true + _ -> false + end - is_followed = - case WandererApp.MapCharacterSettingsRepo.get_by_map(map_id, character_id) do - {:ok, setting} -> setting.followed == true - _ -> false - end + must_select? = is_user_character && (is_select_on_spash || is_followed) - must_select? = is_user_character && (is_select_on_spash || is_followed) - if not must_select? do + if not must_select? do + socket + else + # Check if we already selected this exact system for this char: + last_selected = + WandererApp.Cache.lookup!( + "char:#{character_id}:map:#{map_id}:last_selected_system_id", + nil + ) + + if last_selected == solar_system_id do + # same system => skip socket else - # Check if we already selected this exact system for this char: - last_selected = - WandererApp.Cache.lookup!( - "char:#{character_id}:map:#{map_id}:last_selected_system_id", - nil - ) + # new system => update cache + push event + WandererApp.Cache.put( + "char:#{character_id}:map:#{map_id}:last_selected_system_id", + solar_system_id + ) - if last_selected == solar_system_id do - # same system => skip - socket - else - # new system => update cache + push event - WandererApp.Cache.put( - "char:#{character_id}:map:#{map_id}:last_selected_system_id", - solar_system_id - ) - - socket - |> MapEventHandler.push_map_event("select_system", solar_system_id) - end + socket + |> MapEventHandler.push_map_event("select_system", solar_system_id) end + end end def handle_server_event(%{event: :kills_updated, payload: kills}, socket) do diff --git a/mix.exs b/mix.exs index bfa705c2..dc2ff741 100644 --- a/mix.exs +++ b/mix.exs @@ -3,7 +3,7 @@ defmodule WandererApp.MixProject do @source_url "https://github.com/wanderer-industries/wanderer" - @version "1.42.3" + @version "1.43.6" def project do [ diff --git a/priv/posts/2025/01-20-structure-widget.md b/priv/posts/2025/01-20-structure-widget.md new file mode 100644 index 00000000..032a09ba --- /dev/null +++ b/priv/posts/2025/01-20-structure-widget.md @@ -0,0 +1,151 @@ +%{ +title: "Managing Upwell Structures & Timers with the Structures Widget", +author: "Wanderer Team", +cover_image_uri: "/images/news/01-20-structure-widget/cover.png", +tags: ~w(interface guide map structures), +description: "Learn how to track structure information using the Structures Widget." +} + +--- + +### Introduction + +Upwell structures like **Astrahus**, **Athanor**, and more are key strategic points in EVE Online. Staying informed about their statuses—whether they’re anchoring, powered, or reinforced—helps you plan defenses, coordinate attacks, and align with allies. Our **Structures Widget** simplifies the process by allowing you to: + +- Copy structure information directly from the in-game Directional Scanner (`D-Scan`) and paste it into the widget. +- Keep track of **anchoring** or **reinforced** timers, including exact vulnerability windows. +- Share real-time data across the map with your corporation or alliance, ensuring everyone is on the same page. + +In this guide, we’ll explore how to enable the Structures Widget, manage structure data, and make use of the built-in API for remote structure updates. + +--- + +### 1. Enabling the Structure Widget + +![Enabling the Structures Widget](/images/news/01-20-structure-widget/enable-widget.png "Enable Structures Widget") + +1. **Open the Map:** +2. **Locate the Widget Settings:** By default, the structure widget panel is not visible. Enable it by going to menu -> map settings -> widgets. +3. **Add the Structures Widget:** Click the checkbox for **Structures** from the list of available widgets. + +> **Tip:** Rearrange your widgets by dragging them around the panel to suit your workflow. + +--- + +### 2. Overview of the Structures Widget + +![Structures Widget Overview](/images/news/01-20-structure-widget/cover.png "Structures Widget") + +Once enabled, the **Structures Widget** appears in the map. It shows: + +- **Structure Type** (Astrahus, Fortizar, etc.) +- **Structure Name** (auto-detected if you paste from D-Scan) +- **Owner** (Corporation ticker) +- **Status** (Powered, Anchoring, Low Power, Reinforced, etc.) +- **Timer** (Reinforced or anchoring end time) + +You can **click** or **double-click** on an entry to edit details like the structure’s owner or add notes about the structure’s purpose or location. + +--- + +### 3. Adding Structures via Copy & Paste + +A fast way to add structure data is by copying from in-game D-Scan or show-info panels: + +1. **In EVE Online:** Open the D-Scan window or structure context menu, select the relevant lines of text, and press **Ctrl + C**. +2. **In the Widget:** Focus on the Structures Widget, click in the widget area, and press **Ctrl + V** to paste or use the **blue** add structure info button. +3. The widget automatically parses the structure names and types. You can also add owners and notes manually. + +This eliminates manual typing and reduces the chance of errors, especially useful when scanning multiple systems. + +--- + +### 4. Tracking Reinforced Timers + +When a structure is in a **Reinforced** or **Anchoring** state, we have a timer to note when it becomes vulnerable or completes anchoring: + +- **Timer Field:** If the structure’s status is set to “Reinforced” or “Anchoring,” the widget enables a **Calendar** pop-up where you can set the _end time_. + +Keep your fleet prepared by referencing this schedule. When the timer hits zero, the structure becomes vulnerable (or finishes anchoring). + +--- + +### 5. Editing and Deleting Structures + +1. **Single-click** a structure entry to select it. +2. Press **Delete** (or **Backspace**) to remove it entirely—useful when clearing out old data or removing outdated structures. +3. **Double-click** to open the **Edit Dialog**: + - Change **Name**, **Owner**, or **Status**. + - Update or remove **Reinforced** timers. + - Add or edit **Notes**. + +Any changes made here are immediately visible to other map users. + +--- + +### 6. API Integration for Automated Timers + +Beyond the in-app widget, there is a dedicated API endpoint to fetch or update structure timers programmatically. This allows advanced users and third-party applications to seamlessly incorporate structure data. + +**Example API Request/Response**: + +```bash +curl -H "Authorization: Bearer YOUR_API_TOKEN" \ +"https://wanderer.yourdomain.space/api/map/structure-timers?slug=yourmap" + + "data": [ + { + "name": "Overlook Hotel", + "status": "Reinforced", + "notes": null, + "owner_id": null, + "solar_system_id": 31000515, + "solar_system_name": "J114942", + "character_eve_id": "2122839817", + "system_id": "4865aec4-b69d-4524-91d3-250b0556322b", + "end_time": "2025-01-22T23:42:03.000000Z", + "owner_name": null, + "owner_ticker": null, + "structure_type": "Astrahus", + "structure_type_id": "35832" + }, + { + "name": "Some Structure", + "status": "Reinforced", + "notes": null, + "owner_id": null, + "solar_system_id": 3100229, + "solar_system_name": "somecustomname", + "character_eve_id": "some name", + "system_id": "ae779ed6-92b3-4349-899d-f1bdf299082f", + "end_time": "2025-01-16T03:04:00.000000Z", + "owner_name": null, + "owner_ticker": null, + "structure_type": "Athanor", + "structure_type_id": "35835" + } + ] +``` + + +With this API, you could, for example, build automated pings on Slack/Discord when timers are about to expire or display status updates on a custom web dashboard. + +> **Note:** Ensure your API token (`Bearer YOUR_API_TOKEN`) matches the api key generated for you map. + +--- + +### 7. Best Practices & Tips + +- **Keep Data Fresh:** Update timers as soon as possible after a structure enters reinforcement. This keeps your corporation or alliance fully informed. +- **Use Notes Effectively:** Add details such as final reinforcement phases or relevant system intel (e.g., known hostiles, safe spots) to help allies plan more effectively. + +--- + +## Conclusion + +The **Structures Widget** is your central hub for monitoring, updating, and sharing information about Upwell structures across New Eden. From real-time timer tracking to simple copy-and-paste integration with D-Scan, this widget streamlines group operations and cuts down on manual data entry. + +Whether you’re a solo explorer managing a personal citadel network or a fleet commander overseeing multiple staging systems, the Structures Widget and its accompanying API ensure you’ll always have up-to-date intel on the structures that matter most. + +Fly safe, +**The Wanderer Team** diff --git a/priv/repo/data/wormholes.json b/priv/repo/data/wormholes.json index f86c4e88..e030bd1d 100644 --- a/priv/repo/data/wormholes.json +++ b/priv/repo/data/wormholes.json @@ -222,7 +222,7 @@ { "mass_regen": 500000000, "dest": "hs", - "src": ["c3"], + "src": ["c3", "c4-shattered"], "static": true, "max_mass_per_jump": 300000000, "lifetime": "24",