Compare commits

..

3 Commits

Author SHA1 Message Date
Dmitry Popov
b1f1098df6 chore: release version v1.13.7 2024-10-31 22:56:59 +01:00
Dmitry Popov
ed5d824c0a refactor(Map): Map event handling refactoring 2024-10-31 19:19:23 +01:00
Dmitry Popov
d8e4631981 refactor(Map): Map event handling refactoring 2024-10-31 19:19:07 +01:00
65 changed files with 746 additions and 1656 deletions

View File

@@ -1,7 +1,12 @@
FROM elixir:1.17-otp-27 FROM elixir:1.16-otp-25
RUN apt install -yq curl gnupg RUN apt update -yq
RUN apt install -yq curl gnupg mc inotify-tools
RUN apt --fix-broken install RUN apt --fix-broken install
RUN apt remove -y nodejs nodejs-doc
RUN curl -sL https://deb.nodesource.com/setup_18.x | bash -
RUN apt install -y nodejs
RUN npm install --global yarn
RUN mix local.hex --force RUN mix local.hex --force

View File

@@ -2,7 +2,7 @@ version: "0.1"
services: services:
db: db:
image: postgres:13-alpine image: postgres:14.3
restart: always restart: always
environment: environment:
POSTGRES_USER: postgres POSTGRES_USER: postgres
@@ -10,13 +10,13 @@ services:
ports: ports:
- "5432:5432" - "5432:5432"
volumes: volumes:
- db-new:/var/lib/postgresql/data - db:/var/lib/postgresql/data
wanderer: wanderer:
environment: environment:
PORT: 8000 PORT: 8000
DB_HOST: db DB_HOST: db
WEB_APP_URL: "http://localhost:8000" WEB_APP_URL: "http://localhost:4444"
ERL_AFLAGS: "-kernel shell_history enabled" ERL_AFLAGS: "-kernel shell_history enabled"
build: build:
context: . context: .
@@ -33,4 +33,4 @@ services:
volumes: volumes:
elixir-artifacts: {} elixir-artifacts: {}
db-new: {} db: {}

View File

@@ -18,4 +18,4 @@ jobs:
key: ${{ secrets.SSH_KEY }} key: ${{ secrets.SSH_KEY }}
port: ${{ secrets.PORT }} port: ${{ secrets.PORT }}
script: | script: |
/home/wanderer/app/deploy.sh ${{ github.event.release.tag_name }} /app/release/linux/deploy.sh ${{ github.event.release.tag_name }}

View File

@@ -1,3 +1,3 @@
erlang 26.2.5.5 erlang 25.3
elixir 1.17.3-otp-26 elixir 1.16-otp-25
nodejs 18.0.0 nodejs 18.0.0

View File

@@ -1,139 +1,6 @@
# Change Log # Change Log
<!-- changelog --> <!-- changelog -->
## [v1.18.0](https://github.com/wanderer-industries/wanderer/compare/v1.17.0...v1.18.0) (2024-11-16)
### Features:
* Map: a lot of design issues
## [v1.17.0](https://github.com/wanderer-industries/wanderer/compare/v1.16.1...v1.17.0) (2024-11-15)
### Features:
* Signatures: Add user setting to show Description in a separate column
## [v1.16.1](https://github.com/wanderer-industries/wanderer/compare/v1.16.0...v1.16.1) (2024-11-15)
### Bug Fixes:
* Signatures: Fix signature stored filters
## [v1.16.0](https://github.com/wanderer-industries/wanderer/compare/v1.15.5...v1.16.0) (2024-11-15)
### Features:
* Signatures: Add additional filters support to signature list, show description icon
## [v1.15.5](https://github.com/wanderer-industries/wanderer/compare/v1.15.4...v1.15.5) (2024-11-14)
## [v1.15.4](https://github.com/wanderer-industries/wanderer/compare/v1.15.3...v1.15.4) (2024-11-14)
### Bug Fixes:
* Core: Untracked characters still tracked on map (#63)
## [v1.15.3](https://github.com/wanderer-industries/wanderer/compare/v1.15.2...v1.15.3) (2024-11-13)
## [v1.15.2](https://github.com/wanderer-industries/wanderer/compare/v1.15.1...v1.15.2) (2024-11-07)
## [v1.15.1](https://github.com/wanderer-industries/wanderer/compare/v1.15.0...v1.15.1) (2024-11-07)
### Bug Fixes:
* Dev: Update .devcontainer instructions
## [v1.15.0](https://github.com/wanderer-industries/wanderer/compare/v1.14.1...v1.15.0) (2024-11-07)
### Features:
* Connections: Add connection mark EOL time (#56)
## [v1.14.1](https://github.com/wanderer-industries/wanderer/compare/v1.14.0...v1.14.1) (2024-11-06)
### Bug Fixes:
* Core: Fix character tracking permissions
## [v1.14.0](https://github.com/wanderer-industries/wanderer/compare/v1.13.12...v1.14.0) (2024-11-05)
### Features:
* ACL: Add an ability to assign member role without DnD
## [v1.13.12](https://github.com/wanderer-industries/wanderer/compare/v1.13.11...v1.13.12) (2024-11-04)
### Bug Fixes:
* Map: Fix system revert issues
## [v1.13.11](https://github.com/wanderer-industries/wanderer/compare/v1.13.10...v1.13.11) (2024-11-02)
### Bug Fixes:
* Map: Fix system revert issues
## [v1.13.10](https://github.com/wanderer-industries/wanderer/compare/v1.13.9...v1.13.10) (2024-11-01)
### Bug Fixes:
* Map: Fix system revert issues
## [v1.13.9](https://github.com/wanderer-industries/wanderer/compare/v1.13.8...v1.13.9) (2024-11-01)
## [v1.13.8](https://github.com/wanderer-industries/wanderer/compare/v1.13.7...v1.13.8) (2024-10-28)
## [v1.13.0](https://github.com/wanderer-industries/wanderer/compare/v1.12.11...v1.13.0) (2024-10-28) ## [v1.13.0](https://github.com/wanderer-industries/wanderer/compare/v1.12.11...v1.13.0) (2024-10-28)

View File

@@ -20,11 +20,11 @@ Interested to learn more? [Check more on our website](https://wanderer.ltd/news)
Wanderer is open source project and we have a free as in beer and self-hosted solution called [Wanderer Community Edition (CE)](https://wanderer.ltd/news/community-edition). Here are the differences between Wanderer and Wanderer CE: Wanderer is open source project and we have a free as in beer and self-hosted solution called [Wanderer Community Edition (CE)](https://wanderer.ltd/news/community-edition). Here are the differences between Wanderer and Wanderer CE:
| | Wanderer Cloud | Wanderer Community Edition | | | Wanderer Cloud | Wanderer Community Edition |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------- | ------------- | ------------- |
| **Infrastructure management** | Easy and convenient. It takes 2 minutes to register your character and create a map. We manage everything so you dont have to worry about anything and can focus on gameplay. | You do it all yourself. You need to get a server and you need to manage your infrastructure. You are responsible for installation, maintenance, upgrades, server capacity, uptime, backup, security, stability, consistency, loading time and so on. | | **Infrastructure management** | Easy and convenient. It takes 2 minutes to register your character and create a map. We manage everything so you dont have to worry about anything and can focus on gameplay. | You do it all yourself. You need to get a server and you need to manage your infrastructure. You are responsible for installation, maintenance, upgrades, server capacity, uptime, backup, security, stability, consistency, loading time and so on.|
| **Release schedule** | Continuously developed and improved with new features and updates multiple times per week. | Latest features and improvements won't be immediately available. | | **Release schedule** | Continuously developed and improved with new features and updates multiple times per week. | Latest features and improvements won't be immediately available.|
| **Server location** | All visitor data is exclusively processed on EU-owned cloud infrastructure. We keep your site data on a secure, encrypted and green energy powered server in Germany. This ensures that your site data is protected by the strict European Union data privacy laws and ensures compliance with GDPR. Your website data never leaves the EU. | You have full control and can host your instance on any server in any country that you wish. Host it on a server in your basement or host it with any cloud provider wherever you want, even those that are not GDPR compliant. | | **Server location** | All visitor data is exclusively processed on EU-owned cloud infrastructure. We keep your site data on a secure, encrypted and green energy powered server in Germany. This ensures that your site data is protected by the strict European Union data privacy laws and ensures compliance with GDPR. Your website data never leaves the EU. | You have full control and can host your instance on any server in any country that you wish. Host it on a server in your basement or host it with any cloud provider wherever you want, even those that are not GDPR compliant.|
Interested in self-hosting Wanderer CE on your server? Take a look at our [Wanderer CE installation instructions](https://github.com/wanderer-industries/community-edition/). Interested in self-hosting Wanderer CE on your server? Take a look at our [Wanderer CE installation instructions](https://github.com/wanderer-industries/community-edition/).
@@ -54,13 +54,7 @@ Now you can visit [`localhost:8000`](http://localhost:8000) from your browser.
#### Using .devcontainer #### Using .devcontainer
- Run devcontainer - Run devcontainer
- Install additional dependencies inside Dev container - See how to start server in #setup section
- `root@0d0a785313b6:/app# apt update`
- `root@0d0a785313b6:/app# curl -sL https://deb.nodesource.com/setup_18.x | bash -`
- `root@0d0a785313b6:/app# apt-get install nodejs inotify-tools -y`
- `root@0d0a785313b6:/app# mix setup`
- See how to run server in #Run section
#### Using nix flakes #### Using nix flakes

View File

@@ -15,10 +15,11 @@ const ErrorFallback = () => {
}; };
export default function MapRoot({ hooks }) { export default function MapRoot({ hooks }) {
const mapRef = useRef<MapHandlers>(null);
const providerRef = useRef<MapHandlers>(null); const providerRef = useRef<MapHandlers>(null);
const hooksRef = useRef<any>(hooks); const hooksRef = useRef<any>(hooks);
const mapperHandlerRefs = useRef([providerRef]); const mapperHandlerRefs = useRef([mapRef, providerRef]);
const { handleCommand, handleMapEvent, handleMapEvents } = useMapperHandlers(mapperHandlerRefs.current, hooksRef); const { handleCommand, handleMapEvent, handleMapEvents } = useMapperHandlers(mapperHandlerRefs.current, hooksRef);
@@ -40,7 +41,7 @@ export default function MapRoot({ hooks }) {
return ( return (
<PrimeReactProvider> <PrimeReactProvider>
<MapRootProvider fwdRef={providerRef} outCommand={handleCommand}> <MapRootProvider fwdRef={providerRef} outCommand={handleCommand} mapRef={mapRef}>
<ErrorBoundary FallbackComponent={ErrorFallback} onError={logError}> <ErrorBoundary FallbackComponent={ErrorFallback} onError={logError}>
<ReactFlowProvider> <ReactFlowProvider>
<MapRootContent /> <MapRootContent />

View File

@@ -1,19 +1,20 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import clsx from 'clsx'; import clsx from 'clsx';
import { useAutoAnimate } from '@formkit/auto-animate/react'; import { useAutoAnimate } from '@formkit/auto-animate/react';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts'; import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts';
import { CharacterTypeRaw } from '@/hooks/Mapper/types'; import { CharacterTypeRaw } from '@/hooks/Mapper/types';
import { emitMapEvent } from '@/hooks/Mapper/events';
const Characters = ({ data }: { data: CharacterTypeRaw[] }) => { const Characters = ({ data }: { data: CharacterTypeRaw[] }) => {
const [parent] = useAutoAnimate(); const [parent] = useAutoAnimate();
const { mapRef } = useMapRootState();
const handleSelect = useCallback((character: CharacterTypeRaw) => { const handleSelect = useCallback(
emitMapEvent({ (character: CharacterTypeRaw) => {
name: Commands.centerSystem, mapRef.current?.command(Commands.centerSystem, character?.location?.solar_system_id?.toString());
data: character?.location?.solar_system_id?.toString(), },
}); [mapRef],
}, []); );
const items = data.map(character => ( const items = data.map(character => (
<li <li

View File

@@ -1,25 +1,25 @@
import * as React from 'react'; import { RefObject, useCallback, useRef, useState } from 'react';
import { useCallback, useRef, useState } from 'react';
import { ContextMenu } from 'primereact/contextmenu'; import { ContextMenu } from 'primereact/contextmenu';
import { Commands, OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts'; import { Commands, MapHandlers, OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts';
import { WaypointSetContextHandler } from '@/hooks/Mapper/components/contexts/types.ts'; import { WaypointSetContextHandler } from '@/hooks/Mapper/components/contexts/types.ts';
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts'; import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
import * as React from 'react';
import { SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types'; import { SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types';
import { emitMapEvent } from '@/hooks/Mapper/events';
interface UseContextMenuSystemHandlersProps { interface UseContextMenuSystemHandlersProps {
hubs: string[]; hubs: string[];
outCommand: OutCommandHandler; outCommand: OutCommandHandler;
mapRef: RefObject<MapHandlers>;
} }
export const useContextMenuSystemInfoHandlers = ({ hubs, outCommand }: UseContextMenuSystemHandlersProps) => { export const useContextMenuSystemInfoHandlers = ({ hubs, outCommand, mapRef }: UseContextMenuSystemHandlersProps) => {
const contextMenuRef = useRef<ContextMenu | null>(null); const contextMenuRef = useRef<ContextMenu | null>(null);
const [system, setSystem] = useState<string>(); const [system, setSystem] = useState<string>();
const routeRef = useRef<(SolarSystemStaticInfoRaw | undefined)[]>([]); const routeRef = useRef<(SolarSystemStaticInfoRaw | undefined)[]>([]);
const ref = useRef({ hubs, system, outCommand }); const ref = useRef({ hubs, system, outCommand, mapRef });
ref.current = { hubs, system, outCommand }; ref.current = { hubs, system, outCommand, mapRef };
const open = useCallback( const open = useCallback(
(ev: React.SyntheticEvent, systemId: string, route: (SolarSystemStaticInfoRaw | undefined)[]) => { (ev: React.SyntheticEvent, systemId: string, route: (SolarSystemStaticInfoRaw | undefined)[]) => {
@@ -48,7 +48,7 @@ export const useContextMenuSystemInfoHandlers = ({ hubs, outCommand }: UseContex
}, []); }, []);
const onAddSystem = useCallback(() => { const onAddSystem = useCallback(() => {
const { system: solarSystemId, outCommand } = ref.current; const { system: solarSystemId, outCommand, mapRef } = ref.current;
if (!solarSystemId) { if (!solarSystemId) {
return; return;
} }
@@ -60,11 +60,7 @@ export const useContextMenuSystemInfoHandlers = ({ hubs, outCommand }: UseContex
}, },
}); });
setTimeout(() => { setTimeout(() => {
emitMapEvent({ mapRef.current?.command(Commands.centerSystem, solarSystemId);
name: Commands.centerSystem,
data: solarSystemId,
});
setSystem(undefined); setSystem(undefined);
}, 200); }, 200);
}, []); }, []);

View File

@@ -1,11 +1,10 @@
import { ForwardedRef, forwardRef, MouseEvent, useCallback, useEffect } from 'react'; import { ForwardedRef, forwardRef, MouseEvent, useCallback, useEffect, useRef } from 'react';
import ReactFlow, { import ReactFlow, {
Background, Background,
ConnectionMode, ConnectionMode,
Edge, Edge,
MiniMap, MiniMap,
Node, Node,
NodeChange,
NodeDragHandler, NodeDragHandler,
OnConnect, OnConnect,
OnMoveEnd, OnMoveEnd,
@@ -14,6 +13,7 @@ import ReactFlow, {
SelectionMode, SelectionMode,
useEdgesState, useEdgesState,
useNodesState, useNodesState,
NodeChange,
useReactFlow, useReactFlow,
} from 'reactflow'; } from 'reactflow';
import 'reactflow/dist/style.css'; import 'reactflow/dist/style.css';
@@ -21,6 +21,7 @@ import classes from './Map.module.scss';
import './styles/neon-theme.scss'; import './styles/neon-theme.scss';
import './styles/eve-common.scss'; import './styles/eve-common.scss';
import { MapProvider, useMapState } from './MapProvider'; import { MapProvider, useMapState } from './MapProvider';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useMapHandlers, useUpdateNodes } from './hooks'; import { useMapHandlers, useUpdateNodes } from './hooks';
import { MapHandlers, OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts'; import { MapHandlers, OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts';
import { import {
@@ -36,6 +37,7 @@ import { SESSION_KEY } from '@/hooks/Mapper/constants.ts';
import { SolarSystemConnection, SolarSystemRawType } from '@/hooks/Mapper/types'; import { SolarSystemConnection, SolarSystemRawType } from '@/hooks/Mapper/types';
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts'; import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
import { NodeSelectionMouseHandler } from '@/hooks/Mapper/components/contexts/types.ts'; import { NodeSelectionMouseHandler } from '@/hooks/Mapper/components/contexts/types.ts';
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
const DEFAULT_VIEW_PORT = { zoom: 1, x: 0, y: 0 }; const DEFAULT_VIEW_PORT = { zoom: 1, x: 0, y: 0 };
@@ -91,14 +93,12 @@ interface MapCompProps {
refn: ForwardedRef<MapHandlers>; refn: ForwardedRef<MapHandlers>;
onCommand: OutCommandHandler; onCommand: OutCommandHandler;
onSelectionChange: OnMapSelectionChange; onSelectionChange: OnMapSelectionChange;
onManualDelete(systems: string[]): void;
onConnectionInfoClick?(e: SolarSystemConnection): void; onConnectionInfoClick?(e: SolarSystemConnection): void;
onSelectionContextMenu?: NodeSelectionMouseHandler; onSelectionContextMenu?: NodeSelectionMouseHandler;
minimapClasses?: string; minimapClasses?: string;
isShowMinimap?: boolean; isShowMinimap?: boolean;
onSystemContextMenu: (event: MouseEvent<Element>, systemId: string) => void; onSystemContextMenu: (event: MouseEvent<Element>, systemId: string) => void;
showKSpaceBG?: boolean; showKSpaceBG?: boolean;
isThickConnections?: boolean;
} }
const MapComp = ({ const MapComp = ({
@@ -109,10 +109,8 @@ const MapComp = ({
onSystemContextMenu, onSystemContextMenu,
onConnectionInfoClick, onConnectionInfoClick,
onSelectionContextMenu, onSelectionContextMenu,
onManualDelete,
isShowMinimap, isShowMinimap,
showKSpaceBG, showKSpaceBG,
isThickConnections,
}: MapCompProps) => { }: MapCompProps) => {
const { getNode } = useReactFlow(); const { getNode } = useReactFlow();
const [nodes, , onNodesChange] = useNodesState<SolarSystemRawType>(initialNodes); const [nodes, , onNodesChange] = useNodesState<SolarSystemRawType>(initialNodes);
@@ -123,6 +121,14 @@ const MapComp = ({
const { handleRootContext, ...rootCtxProps } = useContextMenuRootHandlers(); const { handleRootContext, ...rootCtxProps } = useContextMenuRootHandlers();
const { handleConnectionContext, ...connectionCtxProps } = useContextMenuConnectionHandlers(); const { handleConnectionContext, ...connectionCtxProps } = useContextMenuConnectionHandlers();
const { update } = useMapState(); const { update } = useMapState();
const {
data: { systems },
} = useMapRootState();
const { deleteSystems } = useDeleteSystems();
const systemsRef = useRef({ systems });
systemsRef.current = { systems };
const onConnect: OnConnect = useCallback( const onConnect: OnConnect = useCallback(
params => { params => {
@@ -180,41 +186,35 @@ const MapComp = ({
const handleNodesChange = useCallback( const handleNodesChange = useCallback(
(changes: NodeChange[]) => { (changes: NodeChange[]) => {
const systemsIdsToRemove: string[] = []; const systemsIdsToRemove: string[] = [];
const nextChanges = changes.reduce((acc, change) => { const nextChanges = changes.reduce((acc, change) => {
if (change.type !== 'remove') { if (change.type === 'remove') {
return [...acc, change]; const node = getNode(change.id);
} const { systems = [] } = systemsRef.current;
if (node?.data?.id && !systems.map(s => s.id).includes(node?.data?.id)) {
const node = getNode(change.id); return [...acc, change];
if (!node) { } else if (!node?.data?.locked) {
return [...acc, change]; systemsIdsToRemove.push(node?.data?.id);
} }
if (node.data.locked) {
return acc; return acc;
} }
systemsIdsToRemove.push(node.data.id);
return [...acc, change]; return [...acc, change];
}, [] as NodeChange[]); }, [] as NodeChange[]);
if (systemsIdsToRemove.length > 0) { if (systemsIdsToRemove.length) {
onManualDelete(systemsIdsToRemove); deleteSystems(systemsIdsToRemove);
} }
onNodesChange(nextChanges); onNodesChange(nextChanges);
}, },
[getNode, onManualDelete, onNodesChange], [deleteSystems, getNode, onNodesChange],
); );
useEffect(() => { useEffect(() => {
update(x => ({ update(x => ({
...x, ...x,
showKSpaceBG: showKSpaceBG, showKSpaceBG: showKSpaceBG,
isThickConnections: isThickConnections,
})); }));
}, [showKSpaceBG, isThickConnections, update]); }, [showKSpaceBG, update]);
return ( return (
<> <>
@@ -238,7 +238,6 @@ const MapComp = ({
onConnectStart={() => update({ isConnecting: true })} onConnectStart={() => update({ isConnecting: true })}
onConnectEnd={() => update({ isConnecting: false })} onConnectEnd={() => update({ isConnecting: false })}
onNodeMouseEnter={(_, node) => update({ hoverNodeId: node.id })} onNodeMouseEnter={(_, node) => update({ hoverNodeId: node.id })}
// onKeyUp=
onNodeMouseLeave={() => update({ hoverNodeId: null })} onNodeMouseLeave={() => update({ hoverNodeId: null })}
onEdgeClick={(_, t) => { onEdgeClick={(_, t) => {
onConnectionInfoClick?.(t.data); onConnectionInfoClick?.(t.data);

View File

@@ -8,7 +8,6 @@ export type MapData = MapUnionTypes & {
hoverNodeId: string | null; hoverNodeId: string | null;
visibleNodes: Set<string>; visibleNodes: Set<string>;
showKSpaceBG: boolean; showKSpaceBG: boolean;
isThickConnections: boolean;
}; };
interface MapProviderProps { interface MapProviderProps {
@@ -18,7 +17,6 @@ interface MapProviderProps {
const INITIAL_DATA: MapData = { const INITIAL_DATA: MapData = {
wormholesData: {}, wormholesData: {},
wormholes: [],
effects: {}, effects: {},
characters: [], characters: [],
userCharacters: [], userCharacters: [],
@@ -31,7 +29,6 @@ const INITIAL_DATA: MapData = {
hoverNodeId: null, hoverNodeId: null,
visibleNodes: new Set(), visibleNodes: new Set(),
showKSpaceBG: false, showKSpaceBG: false,
isThickConnections: false,
}; };
export interface MapContextProps { export interface MapContextProps {

View File

@@ -21,7 +21,7 @@
} }
&.Frigate { &.Frigate {
stroke: #d4f0ff; stroke: #4e62c9;
} }
&.Hovered { &.Hovered {
@@ -37,16 +37,9 @@
} }
&.Frigate { &.Frigate {
stroke: #d4f0ff; stroke: #41acd7;
} }
}
&.Tick {
stroke-width: 3px;
&.Hovered {
stroke-width: 3px;
}
} }
} }
@@ -68,14 +61,6 @@
stroke: #ef7dce; stroke: #ef7dce;
} }
} }
&.Tick {
stroke-width: 5px;
&.TimeCrit {
stroke-width: 6px;
}
}
} }
.ClickPath { .ClickPath {
@@ -108,14 +93,5 @@
width: 5px; width: 5px;
height: 5px; height: 5px;
z-index: 1001; z-index: 1001;
&.Tick {
width: 7px;
height: 7px;
&.Right {
margin-left: 0px;
}
}
} }

View File

@@ -7,54 +7,33 @@ import clsx from 'clsx';
import { MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types'; import { MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
import { PrimeIcons } from 'primereact/api'; import { PrimeIcons } from 'primereact/api';
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper'; import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
const MAP_TRANSLATES: Record<string, string> = { const MAP_TRANSLATES: Record<string, string> = {
[Position.Top]: 'translate(-48%, 0%)', [Position.Top]: 'translate(-50%, 0%)',
[Position.Bottom]: 'translate(-50%, -100%)', [Position.Bottom]: 'translate(-50%, -100%)',
[Position.Left]: 'translate(0%, -50%)', [Position.Left]: 'translate(0%, -50%)',
[Position.Right]: 'translate(-100%, -50%)', [Position.Right]: 'translate(-100%, -50%)',
}; };
const MAP_OFFSETS_TICK: Record<string, { x: number; y: number }> = {
[Position.Top]: { x: 0, y: 3 },
[Position.Bottom]: { x: 0, y: -3 },
[Position.Left]: { x: 3, y: 0 },
[Position.Right]: { x: -3, y: 0 },
};
const MAP_OFFSETS: Record<string, { x: number; y: number }> = {
[Position.Top]: { x: 0, y: 0 },
[Position.Bottom]: { x: 0, y: 0 },
[Position.Left]: { x: 0, y: 0 },
[Position.Right]: { x: 0, y: 0 },
};
export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }: EdgeProps<SolarSystemConnection>) => { export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }: EdgeProps<SolarSystemConnection>) => {
const sourceNode = useStore(useCallback(store => store.nodeInternals.get(source), [source])); const sourceNode = useStore(useCallback(store => store.nodeInternals.get(source), [source]));
const targetNode = useStore(useCallback(store => store.nodeInternals.get(target), [target])); const targetNode = useStore(useCallback(store => store.nodeInternals.get(target), [target]));
const {
data: { isThickConnections },
} = useMapState();
const [hovered, setHovered] = useState(false); const [hovered, setHovered] = useState(false);
const [path, labelX, labelY, sx, sy, tx, ty, sourcePos, targetPos] = useMemo(() => { const [path, labelX, labelY, sx, sy, tx, ty, sourcePos, targetPos] = useMemo(() => {
const { sx, sy, tx, ty, sourcePos, targetPos } = getEdgeParams(sourceNode, targetNode); const { sx, sy, tx, ty, sourcePos, targetPos } = getEdgeParams(sourceNode, targetNode);
const offset = isThickConnections ? MAP_OFFSETS_TICK[targetPos] : MAP_OFFSETS[targetPos];
const [edgePath, labelX, labelY] = getBezierPath({ const [edgePath, labelX, labelY] = getBezierPath({
sourceX: sx - offset.x, sourceX: sx,
sourceY: sy - offset.y, sourceY: sy,
sourcePosition: sourcePos, sourcePosition: sourcePos,
targetPosition: targetPos, targetPosition: targetPos,
targetX: tx + offset.x, targetX: tx,
targetY: ty + offset.y, targetY: ty,
}); });
return [edgePath, labelX, labelY, sx, sy, tx, ty, sourcePos, targetPos]; return [edgePath, labelX, labelY, sx, sy, tx, ty, sourcePos, targetPos];
}, [isThickConnections, sourceNode, targetNode]); }, [sourceNode, targetNode]);
if (!sourceNode || !targetNode || !data) { if (!sourceNode || !targetNode || !data) {
return null; return null;
@@ -65,7 +44,6 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
<path <path
id={`back_${id}`} id={`back_${id}`}
className={clsx(classes.EdgePathBack, { className={clsx(classes.EdgePathBack, {
[classes.Tick]: isThickConnections,
[classes.TimeCrit]: data.time_status === TimeStatus.eol, [classes.TimeCrit]: data.time_status === TimeStatus.eol,
[classes.Hovered]: hovered, [classes.Hovered]: hovered,
})} })}
@@ -76,7 +54,6 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
<path <path
id={`front_${id}`} id={`front_${id}`}
className={clsx(classes.EdgePathFront, { className={clsx(classes.EdgePathFront, {
[classes.Tick]: isThickConnections,
[classes.Hovered]: hovered, [classes.Hovered]: hovered,
[classes.MassVerge]: data.mass_status === MassState.verge, [classes.MassVerge]: data.mass_status === MassState.verge,
[classes.MassHalf]: data.mass_status === MassState.half, [classes.MassHalf]: data.mass_status === MassState.half,
@@ -98,19 +75,11 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
<EdgeLabelRenderer> <EdgeLabelRenderer>
<div <div
className={clsx( className={clsx(classes.Handle, 'react-flow__handle absolute nodrag pointer-events-none')}
classes.Handle,
{ [classes.Tick]: isThickConnections, [classes.Right]: Position.Right === sourcePos },
'react-flow__handle absolute nodrag pointer-events-none',
)}
style={{ transform: `${MAP_TRANSLATES[sourcePos]} translate(${sx}px,${sy}px)` }} style={{ transform: `${MAP_TRANSLATES[sourcePos]} translate(${sx}px,${sy}px)` }}
/> />
<div <div
className={clsx( className={clsx(classes.Handle, 'react-flow__handle absolute nodrag pointer-events-none')}
classes.Handle,
{ [classes.Tick]: isThickConnections },
'react-flow__handle absolute nodrag pointer-events-none',
)}
style={{ transform: `${MAP_TRANSLATES[targetPos]} translate(${tx}px,${ty}px)` }} style={{ transform: `${MAP_TRANSLATES[targetPos]} translate(${tx}px,${ty}px)` }}
/> />

View File

@@ -212,14 +212,6 @@ $tooltip-bg: #202020; // Темный фон для подсказок
color: #ffb01d; color: #ffb01d;
} }
/* Firefox kostyl */
@-moz-document url-prefix() {
.classSystemName {
font-family: inherit !important;
font-weight: bold;
}
}
.classSystemName { .classSystemName {
//font-weight: bold; //font-weight: bold;
} }
@@ -270,13 +262,6 @@ $tooltip-bg: #202020; // Темный фон для подсказок
& > * { & > * {
line-height: 10px; line-height: 10px;
} }
/* Firefox kostyl */
@-moz-document url-prefix() {
position: relative;
top: -1px;
}
} }
.Handlers { .Handlers {
@@ -314,25 +299,4 @@ $tooltip-bg: #202020; // Темный фон для подсказок
&.HandleLeft { &.HandleLeft {
left: -2px; left: -2px;
} }
&.Tick {
width: 7px;
height: 7px;
&.HandleTop {
top: -3px;
}
&.HandleRight {
right: -3px;
}
&.HandleBottom {
bottom: -3px;
}
&.HandleLeft {
left: -3px;
}
}
} }

View File

@@ -79,7 +79,6 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
hoverNodeId, hoverNodeId,
visibleNodes, visibleNodes,
showKSpaceBG, showKSpaceBG,
isThickConnections,
}, },
outCommand, outCommand,
} = useMapState(); } = useMapState();
@@ -240,40 +239,28 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
<div onMouseDownCapture={dbClick} className={classes.Handlers}> <div onMouseDownCapture={dbClick} className={classes.Handlers}>
<Handle <Handle
type="source" type="source"
className={clsx(classes.Handle, classes.HandleTop, { className={clsx(classes.Handle, classes.HandleTop, { [classes.selected]: selected })}
[classes.selected]: selected,
[classes.Tick]: isThickConnections,
})}
style={{ visibility: showHandlers ? 'visible' : 'hidden' }} style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
position={Position.Top} position={Position.Top}
id="a" id="a"
/> />
<Handle <Handle
type="source" type="source"
className={clsx(classes.Handle, classes.HandleRight, { className={clsx(classes.Handle, classes.HandleRight, { [classes.selected]: selected })}
[classes.selected]: selected,
[classes.Tick]: isThickConnections,
})}
style={{ visibility: showHandlers ? 'visible' : 'hidden' }} style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
position={Position.Right} position={Position.Right}
id="b" id="b"
/> />
<Handle <Handle
type="source" type="source"
className={clsx(classes.Handle, classes.HandleBottom, { className={clsx(classes.Handle, classes.HandleBottom, { [classes.selected]: selected })}
[classes.selected]: selected,
[classes.Tick]: isThickConnections,
})}
style={{ visibility: showHandlers ? 'visible' : 'hidden' }} style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
position={Position.Bottom} position={Position.Bottom}
id="c" id="c"
/> />
<Handle <Handle
type="source" type="source"
className={clsx(classes.Handle, classes.HandleLeft, { className={clsx(classes.Handle, classes.HandleLeft, { [classes.selected]: selected })}
[classes.selected]: selected,
[classes.Tick]: isThickConnections,
})}
style={{ visibility: showHandlers ? 'visible' : 'hidden' }} style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
position={Position.Left} position={Position.Left}
id="d" id="d"

View File

@@ -5,21 +5,24 @@ import { OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts
export const useMapRemoveSystems = (onSelectionChange: OnMapSelectionChange) => { export const useMapRemoveSystems = (onSelectionChange: OnMapSelectionChange) => {
const rf = useReactFlow(); const rf = useReactFlow();
const ref = useRef({ onSelectionChange, rf }); const ref = useRef({ onSelectionChange });
ref.current = { onSelectionChange, rf }; ref.current = { onSelectionChange };
return useCallback((systems: CommandRemoveSystems) => { return useCallback(
ref.current.rf.deleteElements({ nodes: systems.map(x => ({ id: `${x}` })) }); (systems: CommandRemoveSystems) => {
rf.deleteElements({ nodes: systems.map(x => ({ id: `${x}` })) });
const newSelection = ref.current.rf const newSelection = rf
.getNodes() .getNodes()
.filter(x => !systems.includes(parseInt(x.id))) .filter(x => !systems.includes(parseInt(x.id)))
.filter(x => x.selected) .filter(x => x.selected)
.map(x => x.id); .map(x => x.id);
ref.current.onSelectionChange({ ref.current.onSelectionChange({
systems: newSelection, systems: newSelection,
connections: [], connections: [],
}); });
}, []); },
[rf],
);
}; };

View File

@@ -19,6 +19,8 @@ import {
MapHandlers, MapHandlers,
} from '@/hooks/Mapper/types/mapHandlers.ts'; } from '@/hooks/Mapper/types/mapHandlers.ts';
import { useMapEventListener } from '@/hooks/Mapper/events';
import { import {
useCommandsCharacters, useCommandsCharacters,
useCommandsConnections, useCommandsConnections,
@@ -58,16 +60,13 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
mapInit(data as CommandInit); mapInit(data as CommandInit);
break; break;
case Commands.addSystems: case Commands.addSystems:
setTimeout(() => mapAddSystems(data as CommandAddSystems), 100);
break; break;
case Commands.updateSystems: case Commands.updateSystems:
mapUpdateSystems(data as CommandUpdateSystems); mapUpdateSystems(data as CommandUpdateSystems);
break; break;
case Commands.removeSystems: case Commands.removeSystems:
setTimeout(() => removeSystems(data as CommandRemoveSystems), 100);
break; break;
case Commands.addConnections: case Commands.addConnections:
setTimeout(() => addConnections(data as CommandAddConnections), 100);
break; break;
case Commands.removeConnections: case Commands.removeConnections:
removeConnections(data as CommandRemoveConnections); removeConnections(data as CommandRemoveConnections);
@@ -132,4 +131,20 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
}, },
[], [],
); );
useMapEventListener(event => {
switch (event.name) {
case Commands.addConnections:
addConnections(event.data as CommandAddConnections);
break;
case Commands.addSystems:
mapAddSystems(event.data as CommandAddSystems);
break;
case Commands.removeSystems:
removeSystems(event.data as CommandRemoveSystems);
break;
default:
break;
}
});
}; };

View File

@@ -10,17 +10,13 @@ import {
Setting, Setting,
COSMIC_SIGNATURE, COSMIC_SIGNATURE,
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureSettingsDialog'; } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureSettingsDialog';
import { SignatureGroup } from '@/hooks/Mapper/types';
interface SystemLinkSignatureDialogProps { interface SystemLinkSignatureDialogProps {
data: CommandLinkSignatureToSystem; data: CommandLinkSignatureToSystem;
setVisible: (visible: boolean) => void; setVisible: (visible: boolean) => void;
} }
const signatureSettings: Setting[] = [ const signatureSettings: Setting[] = [{ key: COSMIC_SIGNATURE, name: 'Show Cosmic Signatures', value: true }];
{ key: COSMIC_SIGNATURE, name: 'Show Cosmic Signatures', value: true },
{ key: SignatureGroup.Wormhole, name: 'Wormhole', value: true },
];
export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignatureDialogProps) => { export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignatureDialogProps) => {
const { outCommand } = useMapRootState(); const { outCommand } = useMapRootState();
@@ -63,7 +59,6 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat
> >
<SystemSignaturesContent <SystemSignaturesContent
systemId={`${data.solar_system_source}`} systemId={`${data.solar_system_source}`}
hideLinkedSignatures
settings={signatureSettings} settings={signatureSettings}
onSelect={handleSelect} onSelect={handleSelect}
selectable={true} selectable={true}

View File

@@ -5,7 +5,7 @@ import { SystemViewStandalone, WdTooltip, WdTooltipHandlers } from '@/hooks/Mapp
import { getBackgroundClass, getShapeClass } from '@/hooks/Mapper/components/map/helpers'; import { getBackgroundClass, getShapeClass } from '@/hooks/Mapper/components/map/helpers';
import { MouseEvent, useCallback, useRef, useState } from 'react'; import { MouseEvent, useCallback, useRef, useState } from 'react';
import { Commands } from '@/hooks/Mapper/types'; import { Commands } from '@/hooks/Mapper/types';
import { emitMapEvent } from '@/hooks/Mapper/events'; import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
export type RouteSystemProps = { export type RouteSystemProps = {
destination: number; destination: number;
@@ -88,10 +88,11 @@ export interface RoutesListProps {
export const RoutesList = ({ data, onContextMenu }: RoutesListProps) => { export const RoutesList = ({ data, onContextMenu }: RoutesListProps) => {
const [selected, setSelected] = useState<number | null>(null); const [selected, setSelected] = useState<number | null>(null);
const { mapRef } = useMapRootState();
const handleClick = useCallback( const handleClick = useCallback(
(systemId: number) => emitMapEvent({ name: Commands.centerSystem, data: systemId?.toString() }), (systemId: number) => mapRef.current?.command(Commands.centerSystem, systemId.toString()),
[], [mapRef],
); );
if (!data.has_connection) { if (!data.has_connection) {

View File

@@ -30,6 +30,7 @@ const sortByDist = (a: Route, b: Route) => {
export const RoutesWidgetContent = () => { export const RoutesWidgetContent = () => {
const { const {
data: { selectedSystems, hubs = [], systems, routes }, data: { selectedSystems, hubs = [], systems, routes },
mapRef,
outCommand, outCommand,
} = useMapRootState(); } = useMapRootState();
@@ -41,6 +42,7 @@ export const RoutesWidgetContent = () => {
const { open, ...systemCtxProps } = useContextMenuSystemInfoHandlers({ const { open, ...systemCtxProps } = useContextMenuSystemInfoHandlers({
outCommand, outCommand,
hubs, hubs,
mapRef,
}); });
const preparedHubs = useMemo(() => { const preparedHubs = useMemo(() => {

View File

@@ -1,79 +0,0 @@
.verticalTabsContainer {
display: flex;
width: 100%;
min-height: 300px;
:global {
.p-tabview {
width: 100%;
display: flex;
align-items: flex-start;
}
.p-tabview-panels {
padding: 6px 1rem !important;
flex-grow: 1;
}
.p-tabview-nav-container {
border-right: none;
height: 100%;
}
.p-tabview-nav {
flex-direction: column;
width: 150px;
min-height: 100%;
border: none;
li {
width: 100%;
border-right: 4px solid var(--surface-hover);
background-color: var(--surface-card);
transition:
background-color 200ms,
border-right-color 200ms;
&:hover {
background-color: var(--surface-hover);
border-right: 4px solid var(--surface-100);
}
.p-tabview-nav-link {
transition: color 200ms;
justify-content: flex-end;
padding: 10px;
//background-color: var(--surface-card);
background-color: initial;
border: none;
color: var(--gray-400);
border-radius: initial;
font-weight: 400;
margin: 0;
}
&.p-tabview-selected {
background-color: var(--surface-50);
border-right: 4px solid var(--primary-color);
.p-tabview-nav-link {
font-weight: 600;
color: var(--primary-color);
}
&:hover {
//background-color: var(--surface-hover);
border-right: 4px solid var(--primary-color);
}
}
}
}
.p-tabview-panel {
flex-grow: 1;
}
}
}

View File

@@ -2,10 +2,8 @@ import { Dialog } from 'primereact/dialog';
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import { Button } from 'primereact/button'; import { Button } from 'primereact/button';
import { Checkbox } from 'primereact/checkbox'; import { Checkbox } from 'primereact/checkbox';
import { TabPanel, TabView } from 'primereact/tabview';
import styles from './SystemSignatureSettingsDialog.module.scss';
export type Setting = { key: string; name: string; value: boolean; isFilter?: boolean }; export type Setting = { key: string; name: string; value: boolean };
export const COSMIC_SIGNATURE = 'Cosmic Signature'; export const COSMIC_SIGNATURE = 'Cosmic Signature';
export const COSMIC_ANOMALY = 'Cosmic Anomaly'; export const COSMIC_ANOMALY = 'Cosmic Anomaly';
@@ -26,12 +24,8 @@ export const SystemSignatureSettingsDialog = ({
onSave, onSave,
onCancel, onCancel,
}: SystemSignatureSettingsDialogProps) => { }: SystemSignatureSettingsDialogProps) => {
const [activeIndex, setActiveIndex] = useState(0);
const [settings, setSettings] = useState<Setting[]>(defaultSettings); const [settings, setSettings] = useState<Setting[]>(defaultSettings);
const filterSettings = settings.filter(setting => setting.isFilter);
const userSettings = settings.filter(setting => !setting.isFilter);
const handleSettingsChange = (key: string) => { const handleSettingsChange = (key: string) => {
setSettings(prevState => prevState.map(item => (item.key === key ? { ...item, value: !item.value } : item))); setSettings(prevState => prevState.map(item => (item.key === key ? { ...item, value: !item.value } : item)));
}; };
@@ -41,53 +35,23 @@ export const SystemSignatureSettingsDialog = ({
}, [onSave, settings]); }, [onSave, settings]);
return ( return (
<Dialog header="System Signatures Settings" visible={true} onHide={onCancel} className="w-full max-w-lg"> <Dialog header="Filter signatures" visible draggable={false} style={{ width: '300px' }} onHide={onCancel}>
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className={styles.verticalTabsContainer}> {settings.map(setting => {
<TabView return (
activeIndex={activeIndex} <div key={setting.key} className="flex items-center">
onTabChange={e => setActiveIndex(e.index)} <Checkbox
className={styles.verticalTabView} inputId={setting.key}
> checked={setting.value}
<TabPanel header="Filters" headerClassName={styles.verticalTabHeader}> onChange={() => handleSettingsChange(setting.key)}
<div className="w-full h-full flex flex-col gap-1"> />
{filterSettings.map(setting => { <label htmlFor={setting.key} className="ml-2">
return ( {setting.name}
<div key={setting.key} className="flex items-center"> </label>
<Checkbox </div>
inputId={setting.key} );
checked={setting.value} })}
onChange={() => handleSettingsChange(setting.key)}
/>
<label htmlFor={setting.key} className="ml-2">
{setting.name}
</label>
</div>
);
})}
</div>
</TabPanel>
<TabPanel header="User Interface" headerClassName={styles.verticalTabHeader}>
<div className="w-full h-full flex flex-col gap-1">
{userSettings.map(setting => {
return (
<div key={setting.key} className="flex items-center">
<Checkbox
inputId={setting.key}
checked={setting.value}
onChange={() => handleSettingsChange(setting.key)}
/>
<label htmlFor={setting.key} className="ml-2">
{setting.name}
</label>
</div>
);
})}
</div>
</TabPanel>
</TabView>
</div>
</div> </div>
<div className="flex gap-2 justify-end"> <div className="flex gap-2 justify-end">

View File

@@ -12,7 +12,6 @@ import {
SHIP, SHIP,
DRONE, DRONE,
} from './SystemSignatureSettingsDialog'; } from './SystemSignatureSettingsDialog';
import { SignatureGroup } from '@/hooks/Mapper/types';
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
@@ -20,26 +19,18 @@ import { PrimeIcons } from 'primereact/api';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
const SIGNATURE_SETTINGS_KEY = 'wanderer_system_signature_settings_v3';
export const SHOW_DESCRIPTION_COLUMN_SETTING = 'show_description_column_setting';
const settings: Setting[] = [ const settings: Setting[] = [
{ key: SHOW_DESCRIPTION_COLUMN_SETTING, name: 'Show Description Column', value: false, isFilter: false }, { key: COSMIC_ANOMALY, name: 'Show Anomalies', value: true },
{ key: COSMIC_ANOMALY, name: 'Show Anomalies', value: true, isFilter: true }, { key: COSMIC_SIGNATURE, name: 'Show Cosmic Signatures', value: true },
{ key: COSMIC_SIGNATURE, name: 'Show Cosmic Signatures', value: true, isFilter: true }, { key: DEPLOYABLE, name: 'Show Deployables', value: true },
{ key: DEPLOYABLE, name: 'Show Deployables', value: true, isFilter: true }, { key: STRUCTURE, name: 'Show Structures', value: true },
{ key: STRUCTURE, name: 'Show Structures', value: true, isFilter: true }, { key: STARBASE, name: 'Show Starbase', value: true },
{ key: STARBASE, name: 'Show Starbase', value: true, isFilter: true }, { key: SHIP, name: 'Show Ships', value: true },
{ key: SHIP, name: 'Show Ships', value: true, isFilter: true }, { key: DRONE, name: 'Show Drones And Charges', value: true },
{ key: DRONE, name: 'Show Drones And Charges', value: true, isFilter: true },
{ key: SignatureGroup.Wormhole, name: 'Show Wormholes', value: true, isFilter: true },
{ key: SignatureGroup.RelicSite, name: 'Show Relic Sites', value: true, isFilter: true },
{ key: SignatureGroup.DataSite, name: 'Show Data Sites', value: true, isFilter: true },
{ key: SignatureGroup.OreSite, name: 'Show Ore Sites', value: true, isFilter: true },
{ key: SignatureGroup.GasSite, name: 'Show Gas Sites', value: true, isFilter: true },
{ key: SignatureGroup.CombatSite, name: 'Show Combat Sites', value: true, isFilter: true },
]; ];
const SIGNATURE_SETTINGS_KEY = 'wanderer_system_signature_settings';
const defaultSettings = () => { const defaultSettings = () => {
return [...settings]; return [...settings];
}; };
@@ -100,7 +91,8 @@ export const SystemSignatures = () => {
</InfoDrawer> </InfoDrawer>
<InfoDrawer title={<b className="text-slate-50">How to delete?</b>}> <InfoDrawer title={<b className="text-slate-50">How to delete?</b>}>
For delete any signature first of all you need select before For delete any signature first of all you need select before
<br /> and then use <b className="text-sky-500">Backspace</b> <br /> and then use <b className="text-sky-500">Del</b> or{' '}
<b className="text-sky-500">Backspace</b>
</InfoDrawer> </InfoDrawer>
</div> </div>
) as React.ReactNode, ) as React.ReactNode,

View File

@@ -3,7 +3,6 @@ import { useClipboard } from '@/hooks/Mapper/hooks/useClipboard';
import { parseSignatures } from '@/hooks/Mapper/helpers'; import { parseSignatures } from '@/hooks/Mapper/helpers';
import { Commands, OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts'; import { Commands, OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
import { WdTooltip, WdTooltipHandlers } from '@/hooks/Mapper/components/ui-kit'; import { WdTooltip, WdTooltipHandlers } from '@/hooks/Mapper/components/ui-kit';
import { GROUPS_LIST } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
import { DataTable, DataTableRowClickEvent, DataTableRowMouseEvent, SortOrder } from 'primereact/datatable'; import { DataTable, DataTableRowClickEvent, DataTableRowMouseEvent, SortOrder } from 'primereact/datatable';
import { Column } from 'primereact/column'; import { Column } from 'primereact/column';
@@ -22,7 +21,6 @@ import {
getRowColorByTimeLeft, getRowColorByTimeLeft,
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/helpers'; } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/helpers';
import { import {
renderDescription,
renderIcon, renderIcon,
renderInfoColumn, renderInfoColumn,
renderTimeLeft, renderTimeLeft,
@@ -32,8 +30,7 @@ import { PrimeIcons } from 'primereact/api';
import { SignatureSettings } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings'; import { SignatureSettings } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings';
import { useMapEventListener } from '@/hooks/Mapper/events'; import { useMapEventListener } from '@/hooks/Mapper/events';
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper'; import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
import { COSMIC_SIGNATURE } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureSettingsDialog';
import { SHOW_DESCRIPTION_COLUMN_SETTING } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures';
type SystemSignaturesSortSettings = { type SystemSignaturesSortSettings = {
sortField: string; sortField: string;
sortOrder: SortOrder; sortOrder: SortOrder;
@@ -47,17 +44,10 @@ const SORT_DEFAULT_VALUES: SystemSignaturesSortSettings = {
interface SystemSignaturesContentProps { interface SystemSignaturesContentProps {
systemId: string; systemId: string;
settings: Setting[]; settings: Setting[];
hideLinkedSignatures?: boolean;
selectable?: boolean; selectable?: boolean;
onSelect?: (signature: SystemSignature) => void; onSelect?: (signature: SystemSignature) => void;
} }
export const SystemSignaturesContent = ({ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSelect }: SystemSignaturesContentProps) => {
systemId,
settings,
hideLinkedSignatures,
selectable,
onSelect,
}: SystemSignaturesContentProps) => {
const { outCommand } = useMapRootState(); const { outCommand } = useMapRootState();
const [signatures, setSignatures, signaturesRef] = useRefState<SystemSignature[]>([]); const [signatures, setSignatures, signaturesRef] = useRefState<SystemSignature[]>([]);
@@ -90,36 +80,13 @@ export const SystemSignaturesContent = ({
} }
}, []); }, []);
const groupSettings = useMemo(() => settings.filter(s => (GROUPS_LIST as string[]).includes(s.key)), [settings]);
const showDescriptionColumn = useMemo(
() => settings.find(s => s.key === SHOW_DESCRIPTION_COLUMN_SETTING)?.value,
[settings],
);
const filteredSignatures = useMemo(() => { const filteredSignatures = useMemo(() => {
return signatures return signatures
.filter(x => { .filter(x => settings.find(y => y.key === x.kind)?.value)
if (hideLinkedSignatures && !!x.linked_system) {
return false;
}
const isCosmicSignature = x.kind === COSMIC_SIGNATURE;
if (isCosmicSignature) {
const showCosmicSignatures = settings.find(y => y.key === COSMIC_SIGNATURE)?.value;
if (showCosmicSignatures) {
return !x.group || groupSettings.find(y => y.key === x.group)?.value;
} else {
return !!x.group && groupSettings.find(y => y.key === x.group)?.value;
}
}
return settings.find(y => y.key === x.kind)?.value;
})
.sort((a, b) => { .sort((a, b) => {
return new Date(b.updated_at || 0).getTime() - new Date(a.updated_at || 0).getTime(); return new Date(b.updated_at || 0).getTime() - new Date(a.updated_at || 0).getTime();
}); });
}, [signatures, settings, groupSettings, hideLinkedSignatures]); }, [signatures, settings]);
const handleGetSignatures = useCallback(async () => { const handleGetSignatures = useCallback(async () => {
const { signatures } = await outCommand({ const { signatures } = await outCommand({
@@ -131,6 +98,26 @@ export const SystemSignaturesContent = ({
setSignatures(signatures); setSignatures(signatures);
}, [outCommand, systemId]); }, [outCommand, systemId]);
// const updateSignatures = useCallback(
// async (newSignatures: SystemSignature[], updateOnly: boolean) => {
// const { added, updated, removed } = getActualSigs(signaturesRef.current, newSignatures, updateOnly);
// const { signatures: updatedSignatures } = await outCommand({
// type: OutCommand.updateSignatures,
// data: {
// system_id: systemId,
// added,
// updated,
// removed,
// },
// });
// setSignatures(() => updatedSignatures);
// setSelectedSignatures([]);
// },
// [outCommand, systemId],
// );
const handleUpdateSignatures = useCallback( const handleUpdateSignatures = useCallback(
async (newSignatures: SystemSignature[], updateOnly: boolean) => { async (newSignatures: SystemSignature[], updateOnly: boolean) => {
const { added, updated, removed } = getActualSigs(signaturesRef.current, newSignatures, updateOnly); const { added, updated, removed } = getActualSigs(signaturesRef.current, newSignatures, updateOnly);
@@ -194,7 +181,7 @@ export const SystemSignaturesContent = ({
useHotkey(true, ['a'], handleSelectAll); useHotkey(true, ['a'], handleSelectAll);
useHotkey(false, ['Backspace'], handleDeleteSelected); useHotkey(false, ['Backspace', 'Delete'], handleDeleteSelected);
useEffect(() => { useEffect(() => {
if (selectable) { if (selectable) {
@@ -358,16 +345,6 @@ export const SystemSignaturesContent = ({
style={{ maxWidth: nameColumnWidth }} style={{ maxWidth: nameColumnWidth }}
hidden={compact || medium} hidden={compact || medium}
></Column> ></Column>
{showDescriptionColumn && (
<Column
field="description"
header="Description"
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
body={renderDescription}
hidden={compact}
sortable
></Column>
)}
<Column <Column
field="updated_at" field="updated_at"
header="Updated" header="Updated"
@@ -377,15 +354,13 @@ export const SystemSignaturesContent = ({
sortable sortable
></Column> ></Column>
{!selectable && ( <Column
<Column bodyClassName="p-0 pl-1 pr-2"
bodyClassName="p-0 pl-1 pr-2" field="group"
field="group" body={renderToolbar}
body={renderToolbar} // headerClassName={headerClasses}
// headerClassName={headerClasses} style={{ maxWidth: 26, minWidth: 26, width: 26 }}
style={{ maxWidth: 26, minWidth: 26, width: 26 }} ></Column>
></Column>
)}
</DataTable> </DataTable>
</> </>
)} )}
@@ -395,14 +370,12 @@ export const SystemSignaturesContent = ({
content={hoveredSig ? <SignatureView {...hoveredSig} /> : null} content={hoveredSig ? <SignatureView {...hoveredSig} /> : null}
/> />
{showSignatureSettings && ( <SignatureSettings
<SignatureSettings systemId={systemId}
systemId={systemId} show={showSignatureSettings}
show onHide={() => setShowSignatureSettings(false)}
onHide={() => setShowSignatureSettings(false)} signatureData={selectedSignature}
signatureData={selectedSignature} />
/>
)}
{askUser && ( {askUser && (
<div className="absolute left-[1px] top-[29px] h-[calc(100%-30px)] w-[calc(100%-3px)] bg-stone-900/10 backdrop-blur-sm"> <div className="absolute left-[1px] top-[29px] h-[calc(100%-30px)] w-[calc(100%-3px)] bg-stone-900/10 backdrop-blur-sm">

View File

@@ -1,5 +1,4 @@
export * from './renderIcon'; export * from './renderIcon';
export * from './renderDescription';
export * from './renderName'; export * from './renderName';
export * from './renderTimeLeft'; export * from './renderTimeLeft';
export * from './renderLinkedSystem'; export * from './renderLinkedSystem';

View File

@@ -1,5 +0,0 @@
import { SystemSignature } from '@/hooks/Mapper/types';
export const renderDescription = (row: SystemSignature) => {
return <span title={row?.description}>{row?.description}</span>;
};

View File

@@ -1,9 +1,5 @@
import { PrimeIcons } from 'primereact/api';
import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types'; import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
import { SystemViewStandalone, WHClassView } from '@/hooks/Mapper/components/ui-kit'; import { SystemViewStandalone, WHClassView } from '@/hooks/Mapper/components/ui-kit';
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
import clsx from 'clsx'; import clsx from 'clsx';
import { renderName } from './renderName.tsx'; import { renderName } from './renderName.tsx';
import classes from './renderInfoColumn.module.scss'; import classes from './renderInfoColumn.module.scss';
@@ -36,23 +32,13 @@ export const renderInfoColumn = (row: SystemSignature) => {
</span> </span>
</> </>
)} )}
{row.description && (
<WdTooltipWrapper content={row.description}>
<span className={clsx(PrimeIcons.EXCLAMATION_CIRCLE, 'text-[12px]')}></span>
</WdTooltipWrapper>
)}
</div> </div>
); );
} }
return ( if (row.description != null && row.description.length > 0) {
<div className="flex gap-1 items-center"> return <span title={row.description}>{row.description}</span>;
{renderName(row)}{' '} }
{row.description && (
<WdTooltipWrapper content={row.description}> return renderName(row);
<span className={clsx(PrimeIcons.EXCLAMATION_CIRCLE, 'text-[12px]')}></span>
</WdTooltipWrapper>
)}
</div>
);
}; };

View File

@@ -7,13 +7,13 @@ import { useCallback, useState } from 'react';
import { OnTheMap, RightBar } from '@/hooks/Mapper/components/mapRootContent/components'; import { OnTheMap, RightBar } from '@/hooks/Mapper/components/mapRootContent/components';
import { MapContextMenu } from '@/hooks/Mapper/components/mapRootContent/components/MapContextMenu/MapContextMenu.tsx'; import { MapContextMenu } from '@/hooks/Mapper/components/mapRootContent/components/MapContextMenu/MapContextMenu.tsx';
import { useSkipContextMenu } from '@/hooks/Mapper/hooks/useSkipContextMenu'; import { useSkipContextMenu } from '@/hooks/Mapper/hooks/useSkipContextMenu';
import { MapSettings } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings'; import { MapSettings } from "@/hooks/Mapper/components/mapRootContent/components/MapSettings";
export interface MapRootContentProps {} export interface MapRootContentProps {}
// eslint-disable-next-line no-empty-pattern // eslint-disable-next-line no-empty-pattern
export const MapRootContent = ({}: MapRootContentProps) => { export const MapRootContent = ({}: MapRootContentProps) => {
const { interfaceSettings } = useMapRootState(); const { mapRef, interfaceSettings } = useMapRootState();
const { isShowMenu } = interfaceSettings; const { isShowMenu } = interfaceSettings;
const [showOnTheMap, setShowOnTheMap] = useState(false); const [showOnTheMap, setShowOnTheMap] = useState(false);
@@ -26,7 +26,7 @@ export const MapRootContent = ({}: MapRootContentProps) => {
useSkipContextMenu(); useSkipContextMenu();
return ( return (
<Layout map={<MapWrapper />}> <Layout map={<MapWrapper refn={mapRef} />}>
{!isShowMenu ? ( {!isShowMenu ? (
<div className="absolute top-0 left-14 w-[calc(100%-3.5rem)] h-[calc(100%-3.5rem)] pointer-events-none"> <div className="absolute top-0 left-14 w-[calc(100%-3.5rem)] h-[calc(100%-3.5rem)] pointer-events-none">
<div className="absolute top-0 left-0 w-[calc(100%-3.5rem)] h-full pointer-events-none"> <div className="absolute top-0 left-0 w-[calc(100%-3.5rem)] h-full pointer-events-none">

View File

@@ -1,20 +1,13 @@
import classes from './Connections.module.scss'; import classes from './Connections.module.scss';
import { Sidebar } from 'primereact/sidebar'; import { Sidebar } from 'primereact/sidebar';
import { useEffect, useMemo, useState, useCallback } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { VirtualScroller, VirtualScrollerTemplateOptions } from 'primereact/virtualscroller'; import { VirtualScroller, VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
import clsx from 'clsx'; import clsx from 'clsx';
import { import { ConnectionOutput, OutCommand, Passage, SolarSystemConnection } from '@/hooks/Mapper/types';
ConnectionOutput,
ConnectionInfoOutput,
OutCommand,
Passage,
SolarSystemConnection,
} from '@/hooks/Mapper/types';
import { PassageCard } from './PassageCard'; import { PassageCard } from './PassageCard';
import { InfoDrawer, SystemView } from '@/hooks/Mapper/components/ui-kit'; import { InfoDrawer, SystemView } from '@/hooks/Mapper/components/ui-kit';
import { kgToTons } from '@/hooks/Mapper/utils/kgToTons.ts'; import { kgToTons } from '@/hooks/Mapper/utils/kgToTons.ts';
import { TimeAgo } from '@/hooks/Mapper/components/ui-kit';
const sortByDate = (a: string, b: string) => new Date(a).getTime() - new Date(b).getTime(); const sortByDate = (a: string, b: string) => new Date(a).getTime() - new Date(b).getTime();
@@ -76,44 +69,25 @@ export const Connections = ({ selectedConnection, onHide }: OnTheMapProps) => {
}, [connections, selectedConnection]); }, [connections, selectedConnection]);
const [passages, setPassages] = useState<Passage[]>([]); const [passages, setPassages] = useState<Passage[]>([]);
const [info, setInfo] = useState<ConnectionInfoOutput>(null);
const loadInfo = useCallback(
async (connection: SolarSystemConnection) => {
const result = await outCommand<ConnectionInfoOutput>({
type: OutCommand.getConnectionInfo,
data: {
from: connection.source,
to: connection.target,
},
});
setInfo(result);
},
[outCommand],
);
const loadPassages = useCallback(
async (connection: SolarSystemConnection) => {
const result = await outCommand<ConnectionOutput>({
type: OutCommand.getPassages,
data: {
from: connection.source,
to: connection.target,
},
});
setPassages(result.passages.sort((a, b) => sortByDate(b.inserted_at, a.inserted_at)));
},
[outCommand],
);
useEffect(() => { useEffect(() => {
if (!selectedConnection) { if (!selectedConnection) {
return; return;
} }
loadInfo(selectedConnection);
loadPassages(selectedConnection); const loadInfo = async () => {
const result = await outCommand<ConnectionOutput>({
type: OutCommand.getPassages,
data: {
from: selectedConnection.source,
to: selectedConnection.target,
},
});
setPassages(result.passages.sort((a, b) => sortByDate(b.inserted_at, a.inserted_at)));
};
loadInfo();
}, [selectedConnection]); }, [selectedConnection]);
const approximateMass = useMemo(() => { const approximateMass = useMemo(() => {
@@ -158,10 +132,6 @@ export const Connections = ({ selectedConnection, onHide }: OnTheMapProps) => {
{kgToTons(approximateMass)} {kgToTons(approximateMass)}
</InfoDrawer> </InfoDrawer>
<InfoDrawer title="Mark EOL Time" rightSide>
{info?.marl_eol_time ? <TimeAgo timestamp={info.marl_eol_time} /> : ' unknown '}
</InfoDrawer>
<div className="flex gap-2"></div> <div className="flex gap-2"></div>
</div> </div>

View File

@@ -61,7 +61,6 @@ const CONNECTIONS_CHECKBOXES_PROPS: CheckboxesList = [
const UI_CHECKBOXES_PROPS: CheckboxesList = [ const UI_CHECKBOXES_PROPS: CheckboxesList = [
{ prop: InterfaceStoredSettingsProps.isShowMenu, label: 'Enable compact map menu bar' }, { prop: InterfaceStoredSettingsProps.isShowMenu, label: 'Enable compact map menu bar' },
{ prop: InterfaceStoredSettingsProps.isThickConnections, label: 'Tick connections' },
]; ];
export const MapSettings = ({ show, onHide }: MapSettingsProps) => { export const MapSettings = ({ show, onHide }: MapSettingsProps) => {

View File

@@ -1,14 +1,14 @@
import { Map } from '@/hooks/Mapper/components/map/Map.tsx'; import { Map } from '@/hooks/Mapper/components/map/Map.tsx';
import { useCallback, useRef, useState } from 'react'; import { ForwardedRef, useCallback, useRef, useState } from 'react';
import { OutCommand, OutCommandHandler, SolarSystemConnection } from '@/hooks/Mapper/types'; import { MapHandlers, OutCommand, OutCommandHandler, SolarSystemConnection } from '@/hooks/Mapper/types';
import { MapRootData, useMapRootState } from '@/hooks/Mapper/mapRootProvider'; import { MapRootData, useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts'; import { OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts';
import isEqual from 'lodash.isequal'; import isEqual from 'lodash.isequal';
import { ContextMenuSystem, useContextMenuSystemHandlers } from '@/hooks/Mapper/components/contexts'; import { ContextMenuSystem, useContextMenuSystemHandlers } from '@/hooks/Mapper/components/contexts';
import { import {
SystemCustomLabelDialog, SystemCustomLabelDialog,
SystemLinkSignatureDialog,
SystemSettingsDialog, SystemSettingsDialog,
SystemLinkSignatureDialog,
} from '@/hooks/Mapper/components/mapInterface/components'; } from '@/hooks/Mapper/components/mapInterface/components';
import classes from './MapWrapper.module.scss'; import classes from './MapWrapper.module.scss';
import { Connections } from '@/hooks/Mapper/components/mapRootContent/components/Connections'; import { Connections } from '@/hooks/Mapper/components/mapRootContent/components/Connections';
@@ -20,45 +20,25 @@ import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts';
import { useMapEventListener } from '@/hooks/Mapper/events'; import { useMapEventListener } from '@/hooks/Mapper/events';
import { STORED_INTERFACE_DEFAULT_VALUES } from '@/hooks/Mapper/mapRootProvider/MapRootProvider'; import { STORED_INTERFACE_DEFAULT_VALUES } from '@/hooks/Mapper/mapRootProvider/MapRootProvider';
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
import { useCommonMapEventProcessor } from '@/hooks/Mapper/components/mapWrapper/hooks/useCommonMapEventProcessor.ts'; interface MapWrapperProps {
refn: ForwardedRef<MapHandlers>;
}
// TODO: INFO - this component needs for abstract work with Map instance // TODO: INFO - this component needs for abstract work with Map instance
export const MapWrapper = () => { export const MapWrapper = ({ refn }: MapWrapperProps) => {
const { const {
update, update,
outCommand, outCommand,
data: { selectedConnections, selectedSystems, hubs, systems }, data: { selectedConnections, selectedSystems, hubs, systems },
interfaceSettings: { interfaceSettings: { isShowMenu, isShowMinimap = STORED_INTERFACE_DEFAULT_VALUES.isShowMinimap, isShowKSpace },
isShowMenu,
isShowMinimap = STORED_INTERFACE_DEFAULT_VALUES.isShowMinimap,
isShowKSpace,
isThickConnections,
},
} = useMapRootState(); } = useMapRootState();
const { deleteSystems } = useDeleteSystems();
const { mapRef, runCommand } = useCommonMapEventProcessor();
const { open, ...systemContextProps } = useContextMenuSystemHandlers({ systems, hubs, outCommand }); const { open, ...systemContextProps } = useContextMenuSystemHandlers({ systems, hubs, outCommand });
const { handleSystemMultipleContext, ...systemMultipleCtxProps } = useContextMenuSystemMultipleHandlers(); const { handleSystemMultipleContext, ...systemMultipleCtxProps } = useContextMenuSystemMultipleHandlers();
const [openSettings, setOpenSettings] = useState<string | null>(null); const ref = useRef({ selectedConnections, selectedSystems, systemContextProps, systems });
const [openLinkSignatures, setOpenLinkSignatures] = useState<any | null>(null); ref.current = { selectedConnections, selectedSystems, systemContextProps, systems };
const [openCustomLabel, setOpenCustomLabel] = useState<string | null>(null);
const [selectedConnection, setSelectedConnection] = useState<SolarSystemConnection | null>(null);
const ref = useRef({ selectedConnections, selectedSystems, systemContextProps, systems, deleteSystems });
ref.current = { selectedConnections, selectedSystems, systemContextProps, systems, deleteSystems };
useMapEventListener(event => {
switch (event.name) {
case Commands.linkSignatureToSystem:
setOpenLinkSignatures(event.data);
return true;
}
runCommand(event);
});
const onSelectionChange: OnMapSelectionChange = useCallback( const onSelectionChange: OnMapSelectionChange = useCallback(
({ systems, connections }) => { ({ systems, connections }) => {
@@ -79,6 +59,9 @@ export const MapWrapper = () => {
[update], [update],
); );
const [openSettings, setOpenSettings] = useState<string | null>(null);
const [openLinkSignatures, setOpenLinkSignatures] = useState<any | null>(null);
const [openCustomLabel, setOpenCustomLabel] = useState<string | null>(null);
const handleCommand: OutCommandHandler = useCallback( const handleCommand: OutCommandHandler = useCallback(
event => { event => {
switch (event.type) { switch (event.type) {
@@ -112,19 +95,22 @@ export const MapWrapper = () => {
[open], [open],
); );
const [selectedConnection, setSelectedConnection] = useState<SolarSystemConnection | null>(null);
const handleConnectionDbClick = useCallback((e: SolarSystemConnection) => setSelectedConnection(e), []); const handleConnectionDbClick = useCallback((e: SolarSystemConnection) => setSelectedConnection(e), []);
const handleManualDelete = useCallback((toDelete: string[]) => { useMapEventListener(event => {
const restDel = toDelete.filter(x => ref.current.systems.some(y => y.id === x)); switch (event.name) {
if (restDel.length > 0) { case Commands.linkSignatureToSystem:
ref.current.deleteSystems(restDel); setOpenLinkSignatures(event.data);
return true;
} }
}, []); });
return ( return (
<> <>
<Map <Map
ref={mapRef} ref={refn}
onCommand={handleCommand} onCommand={handleCommand}
onSelectionChange={onSelectionChange} onSelectionChange={onSelectionChange}
onConnectionInfoClick={handleConnectionDbClick} onConnectionInfoClick={handleConnectionDbClick}
@@ -133,8 +119,6 @@ export const MapWrapper = () => {
minimapClasses={!isShowMenu ? classes.MiniMap : undefined} minimapClasses={!isShowMenu ? classes.MiniMap : undefined}
isShowMinimap={isShowMinimap} isShowMinimap={isShowMinimap}
showKSpaceBG={isShowKSpace} showKSpaceBG={isShowKSpace}
onManualDelete={handleManualDelete}
isThickConnections={isThickConnections}
/> />
{openSettings != null && ( {openSettings != null && (

View File

@@ -1,38 +0,0 @@
import { MutableRefObject, useCallback, useEffect, useRef } from 'react';
import { Command, Commands, MapHandlers } from '@/hooks/Mapper/types';
import { MapEvent } from '@/hooks/Mapper/events';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
export const useCommonMapEventProcessor = () => {
const mapRef = useRef<MapHandlers>() as MutableRefObject<MapHandlers>;
const {
data: { systems },
} = useMapRootState();
const refQueue = useRef<MapEvent<Command>[]>([]);
// const ref = useRef({})
const runCommand = useCallback(({ name, data }: MapEvent<Command>) => {
switch (name) {
case Commands.addSystems:
case Commands.removeSystems:
// case Commands.addConnections:
refQueue.current.push({ name, data });
return;
}
// @ts-ignore hz why here type error
mapRef.current?.command(name, data);
}, []);
useEffect(() => {
refQueue.current.forEach(x => mapRef.current?.command(x.name, x.data));
refQueue.current = [];
}, [systems]);
return {
mapRef,
runCommand,
};
};

View File

@@ -4,7 +4,7 @@ import classes from './CharacterCard.module.scss';
import { SystemView } from '@/hooks/Mapper/components/ui-kit/SystemView'; import { SystemView } from '@/hooks/Mapper/components/ui-kit/SystemView';
import { CharacterTypeRaw, WithIsOwnCharacter } from '@/hooks/Mapper/types'; import { CharacterTypeRaw, WithIsOwnCharacter } from '@/hooks/Mapper/types';
import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts'; import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts';
import { emitMapEvent } from '@/hooks/Mapper/events'; import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
type CharacterCardProps = { type CharacterCardProps = {
compact?: boolean; compact?: boolean;
@@ -34,12 +34,11 @@ export const CharacterCard = ({
useSystemsCache, useSystemsCache,
...char ...char
}: CharacterCardProps) => { }: CharacterCardProps) => {
const { mapRef } = useMapRootState();
const handleSelect = useCallback(() => { const handleSelect = useCallback(() => {
emitMapEvent({ mapRef.current?.command(Commands.centerSystem, char?.location?.solar_system_id?.toString());
name: Commands.centerSystem, }, [mapRef, char]);
data: char?.location?.solar_system_id?.toString(),
});
}, [char]);
return ( return (
<div className={clsx(classes.CharacterCard, 'w-full text-xs', 'flex flex-col box-border')} onClick={handleSelect}> <div className={clsx(classes.CharacterCard, 'w-full text-xs', 'flex flex-col box-border')} onClick={handleSelect}>

View File

@@ -31,26 +31,24 @@ export enum InterfaceStoredSettingsProps {
isShowMenu = 'isShowMenu', isShowMenu = 'isShowMenu',
isShowMinimap = 'isShowMinimap', isShowMinimap = 'isShowMinimap',
isShowKSpace = 'isShowKSpace', isShowKSpace = 'isShowKSpace',
isThickConnections = 'isThickConnections',
} }
export type InterfaceStoredSettings = { export type InterfaceStoredSettings = {
isShowMenu: boolean; isShowMenu: boolean;
isShowMinimap: boolean; isShowMinimap: boolean;
isShowKSpace: boolean; isShowKSpace: boolean;
isThickConnections: boolean;
}; };
export const STORED_INTERFACE_DEFAULT_VALUES: InterfaceStoredSettings = { export const STORED_INTERFACE_DEFAULT_VALUES: InterfaceStoredSettings = {
isShowMenu: false, isShowMenu: false,
isShowMinimap: true, isShowMinimap: true,
isShowKSpace: false, isShowKSpace: false,
isThickConnections: false,
}; };
export interface MapRootContextProps { export interface MapRootContextProps {
update: ContextStoreDataUpdate<MapRootData>; update: ContextStoreDataUpdate<MapRootData>;
data: MapRootData; data: MapRootData;
mapRef: RefObject<MapHandlers>;
outCommand: OutCommandHandler; outCommand: OutCommandHandler;
interfaceSettings: InterfaceStoredSettings; interfaceSettings: InterfaceStoredSettings;
setInterfaceSettings: Dispatch<SetStateAction<InterfaceStoredSettings>>; setInterfaceSettings: Dispatch<SetStateAction<InterfaceStoredSettings>>;
@@ -59,6 +57,7 @@ export interface MapRootContextProps {
const MapRootContext = createContext<MapRootContextProps>({ const MapRootContext = createContext<MapRootContextProps>({
update: () => {}, update: () => {},
data: { ...INITIAL_DATA }, data: { ...INITIAL_DATA },
mapRef: { current: null },
// @ts-ignore // @ts-ignore
outCommand: async () => void 0, outCommand: async () => void 0,
interfaceSettings: STORED_INTERFACE_DEFAULT_VALUES, interfaceSettings: STORED_INTERFACE_DEFAULT_VALUES,
@@ -68,6 +67,7 @@ const MapRootContext = createContext<MapRootContextProps>({
type MapRootProviderProps = { type MapRootProviderProps = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
fwdRef: ForwardedRef<any>; fwdRef: ForwardedRef<any>;
mapRef: RefObject<MapHandlers>;
outCommand: OutCommandHandler; outCommand: OutCommandHandler;
} & WithChildren; } & WithChildren;
@@ -78,7 +78,7 @@ const MapRootHandlers = forwardRef(({ children }: WithChildren, fwdRef: Forwarde
}); });
// eslint-disable-next-line react/display-name // eslint-disable-next-line react/display-name
export const MapRootProvider = ({ children, fwdRef, outCommand }: MapRootProviderProps) => { export const MapRootProvider = ({ children, fwdRef, mapRef, outCommand }: MapRootProviderProps) => {
const { update, ref } = useContextStore<MapRootData>({ ...INITIAL_DATA }); const { update, ref } = useContextStore<MapRootData>({ ...INITIAL_DATA });
const [interfaceSettings, setInterfaceSettings] = useLocalStorageState<InterfaceStoredSettings>( const [interfaceSettings, setInterfaceSettings] = useLocalStorageState<InterfaceStoredSettings>(
@@ -94,6 +94,7 @@ export const MapRootProvider = ({ children, fwdRef, outCommand }: MapRootProvide
update, update,
data: ref, data: ref,
outCommand: outCommand, outCommand: outCommand,
mapRef: mapRef,
setInterfaceSettings, setInterfaceSettings,
interfaceSettings, interfaceSettings,
}} }}

View File

@@ -44,79 +44,87 @@ export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
return { return {
command(type, data) { command(type, data) {
switch (type) { switch (type) {
case Commands.init: // USED case Commands.init:
mapInit(data as CommandInit); mapInit(data as CommandInit);
break; break;
case Commands.addSystems: // USED case Commands.addSystems:
addSystems(data as CommandAddSystems); addSystems(data as CommandAddSystems);
setTimeout(() => {
emitMapEvent({ name: Commands.addSystems, data });
}, 100);
break; break;
case Commands.updateSystems: // USED case Commands.updateSystems:
updateSystems(data as CommandUpdateSystems); updateSystems(data as CommandUpdateSystems);
break; break;
case Commands.removeSystems: // USED case Commands.removeSystems:
removeSystems(data as CommandRemoveSystems); removeSystems(data as CommandRemoveSystems);
setTimeout(() => {
emitMapEvent({ name: Commands.removeSystems, data });
}, 100);
break; break;
case Commands.addConnections: // USED case Commands.addConnections:
addConnections(data as CommandAddConnections); addConnections(data as CommandAddConnections);
setTimeout(() => {
emitMapEvent({ name: Commands.addConnections, data });
}, 100);
break; break;
case Commands.removeConnections: // USED case Commands.removeConnections:
removeConnections(data as CommandRemoveConnections); removeConnections(data as CommandRemoveConnections);
break; break;
case Commands.updateConnection: // USED case Commands.updateConnection:
updateConnection(data as CommandUpdateConnection); updateConnection(data as CommandUpdateConnection);
break; break;
case Commands.charactersUpdated: // USED case Commands.charactersUpdated:
charactersUpdated(data as CommandCharactersUpdated); charactersUpdated(data as CommandCharactersUpdated);
break; break;
case Commands.characterAdded: // USED case Commands.characterAdded:
characterAdded(data as CommandCharacterAdded); characterAdded(data as CommandCharacterAdded);
break; break;
case Commands.characterRemoved: // USED case Commands.characterRemoved:
characterRemoved(data as CommandCharacterRemoved); characterRemoved(data as CommandCharacterRemoved);
break; break;
case Commands.characterUpdated: // USED case Commands.characterUpdated:
characterUpdated(data as CommandCharacterUpdated); characterUpdated(data as CommandCharacterUpdated);
break; break;
case Commands.presentCharacters: // USED case Commands.presentCharacters:
presentCharacters(data as CommandPresentCharacters); presentCharacters(data as CommandPresentCharacters);
break; break;
case Commands.mapUpdated: // USED case Commands.mapUpdated:
mapUpdated(data as CommandMapUpdated); mapUpdated(data as CommandMapUpdated);
break; break;
case Commands.routes: case Commands.routes:
mapRoutes(data as CommandRoutes); mapRoutes(data as CommandRoutes);
break; break;
case Commands.centerSystem: // USED case Commands.centerSystem:
// do nothing here // do nothing here
break; break;
case Commands.selectSystem: // USED case Commands.selectSystem:
// do nothing here // do nothing here
break; break;
// case Commands.linkSignatureToSystem: case Commands.linkSignatureToSystem:
// // TODO command data type lost // TODO command data type lost
// // @ts-ignore // @ts-ignore
// emitMapEvent({ name: Commands.linkSignatureToSystem, data }); emitMapEvent({ name: Commands.linkSignatureToSystem, data });
// break; break;
case Commands.killsUpdated: case Commands.killsUpdated:
// do nothing here // do nothing here
break; break;
// case Commands.signaturesUpdated: case Commands.signaturesUpdated:
// // TODO command data type lost // TODO command data type lost
// // @ts-ignore // @ts-ignore
// emitMapEvent({ name: Commands.signaturesUpdated, data }); emitMapEvent({ name: Commands.signaturesUpdated, data });
// break; break;
default: default:
console.warn(`JOipP Interface handlers: Unknown command: ${type}`, data); console.warn(`JOipP Interface handlers: Unknown command: ${type}`, data);
break; break;
} }
emitMapEvent({ name: type, data });
}, },
}; };
}, },

View File

@@ -11,10 +11,6 @@ export type Passage = {
character: PassageLimitedCharacterType; character: PassageLimitedCharacterType;
}; };
export type ConnectionInfoOutput = {
marl_eol_time: string;
};
export type ConnectionOutput = { export type ConnectionOutput = {
passages: Passage[]; passages: Passage[];
}; };

View File

@@ -118,7 +118,6 @@ export enum OutCommand {
getCharacterJumps = 'get_character_jumps', getCharacterJumps = 'get_character_jumps',
getSignatures = 'get_signatures', getSignatures = 'get_signatures',
getSystemStaticInfos = 'get_system_static_infos', getSystemStaticInfos = 'get_system_static_infos',
getConnectionInfo = 'get_connection_info',
updateConnectionTimeStatus = 'update_connection_time_status', updateConnectionTimeStatus = 'update_connection_time_status',
updateConnectionMassStatus = 'update_connection_mass_status', updateConnectionMassStatus = 'update_connection_mass_status',
updateConnectionShipSizeType = 'update_connection_ship_size_type', updateConnectionShipSizeType = 'update_connection_ship_size_type',

View File

@@ -8,7 +8,7 @@ export default {
const selector = '#' + this.el.id; const selector = '#' + this.el.id;
const droppable = new Droppable(containers, { const droppable = new Droppable(containers, {
delay: 100, delay: 150,
draggable: '.draggable', draggable: '.draggable',
dropzone: '.dropzone', dropzone: '.dropzone',
mirror: { mirror: {

View File

@@ -33,7 +33,7 @@
"react-grid-layout": "^1.3.4", "react-grid-layout": "^1.3.4",
"react-hook-form": "^7.53.1", "react-hook-form": "^7.53.1",
"react-usestateref": "^1.0.9", "react-usestateref": "^1.0.9",
"reactflow": "^11.11.4", "reactflow": "^11.10.4",
"rxjs": "^7.8.1", "rxjs": "^7.8.1",
"tailwindcss": "^3.3.6", "tailwindcss": "^3.3.6",
"topbar": "^3.0.0", "topbar": "^3.0.0",

View File

@@ -3280,9 +3280,9 @@ react@18.2.0:
dependencies: dependencies:
loose-envify "^1.1.0" loose-envify "^1.1.0"
reactflow@^11.11.4: reactflow@^11.10.4:
version "11.11.4" version "11.11.4"
resolved "https://registry.yarnpkg.com/reactflow/-/reactflow-11.11.4.tgz#e3593e313420542caed81aecbd73fb9bc6576653" resolved "https://registry.npmjs.org/reactflow/-/reactflow-11.11.4.tgz"
integrity sha512-70FOtJkUWH3BAOsN+LU9lCrKoKbtOPnz2uq0CV2PLdNSwxTXOhCbsZr50GmZ+Rtw3jx8Uv7/vBFtCGixLfd4Og== integrity sha512-70FOtJkUWH3BAOsN+LU9lCrKoKbtOPnz2uq0CV2PLdNSwxTXOhCbsZr50GmZ+Rtw3jx8Uv7/vBFtCGixLfd4Og==
dependencies: dependencies:
"@reactflow/background" "11.3.14" "@reactflow/background" "11.3.14"

View File

@@ -138,7 +138,6 @@ config :ueberauth, WandererApp.Ueberauth.Strategy.Eve.OAuth,
System.get_env("EVE_CLIENT_WITH_CORP_WALLET_SECRET", "<EVE_CLIENT_WITH_CORP_WALLET_SECRET>") System.get_env("EVE_CLIENT_WITH_CORP_WALLET_SECRET", "<EVE_CLIENT_WITH_CORP_WALLET_SECRET>")
config :logger, config :logger,
truncate: :infinity,
level: level:
String.to_existing_atom( String.to_existing_atom(
System.get_env( System.get_env(
@@ -172,65 +171,43 @@ config :wanderer_app, WandererApp.Scheduler,
timeout: :infinity timeout: :infinity
if config_env() == :prod do if config_env() == :prod do
database_unix_socket = database_url =
System.get_env("DATABASE_UNIX_SOCKET") System.get_env("DATABASE_URL") ||
raise """
environment variable DATABASE_URL is missing.
For example: ecto://USER:PASS@HOST/DATABASE
"""
database = maybe_ipv6 =
database_unix_socket config_dir
|> get_var_from_path_or_env("ECTO_IPV6", "false")
|> String.to_existing_atom()
|> case do |> case do
nil -> true -> [:inet6]
System.get_env("DATABASE_URL") || _ -> []
raise """ end
environment variable DATABASE_URL is missing.
For example: ecto://USER:PASS@HOST/DATABASE
"""
_ -> db_ssl_enabled =
System.get_env("DATABASE_NAME") || config_dir
raise """ |> get_var_from_path_or_env("DATABASE_SSL_ENABLED", "false")
environment variable DATABASE_NAME is missing. |> String.to_existing_atom()
For example: "wanderer"
""" db_ssl_verify_none =
config_dir
|> get_var_from_path_or_env("DATABASE_SSL_VERIFY_NONE", "false")
|> String.to_existing_atom()
client_opts =
if db_ssl_verify_none do
[verify: :verify_none]
end end
config :wanderer_app, WandererApp.Repo, config :wanderer_app, WandererApp.Repo,
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10") url: database_url,
ssl: db_ssl_enabled,
if not is_nil(database_unix_socket) do ssl_opts: client_opts,
config :wanderer_app, WandererApp.Repo, pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"),
socket_dir: database_unix_socket, socket_options: maybe_ipv6
database: database
else
db_ssl_enabled =
config_dir
|> get_var_from_path_or_env("DATABASE_SSL_ENABLED", "false")
|> String.to_existing_atom()
db_ssl_verify_none =
config_dir
|> get_var_from_path_or_env("DATABASE_SSL_VERIFY_NONE", "false")
|> String.to_existing_atom()
client_opts =
if db_ssl_verify_none do
[verify: :verify_none]
end
maybe_ipv6 =
config_dir
|> get_var_from_path_or_env("ECTO_IPV6", "false")
|> String.to_existing_atom()
|> case do
true -> [:inet6]
_ -> []
end
config :wanderer_app, WandererApp.Repo,
url: database,
ssl: db_ssl_enabled,
ssl_opts: client_opts,
socket_options: maybe_ipv6
end
# The secret key base is used to sign/encrypt cookies and other secrets. # The secret key base is used to sign/encrypt cookies and other secrets.
# A default value is used in config/dev.exs and config/test.exs but you # A default value is used in config/dev.exs and config/test.exs but you

View File

@@ -3,10 +3,9 @@
# See https://fly.io/docs/reference/configuration/ for information about how to use this file. # See https://fly.io/docs/reference/configuration/ for information about how to use this file.
# #
app = 'wanderer-test' app = 'wanderer'
primary_region = 'ams' primary_region = 'ams'
kill_signal = 'SIGTERM' kill_signal = 'SIGTERM'
swap_size_mb = 512
[build] [build]
@@ -14,14 +13,18 @@ swap_size_mb = 512
release_command = '/app/bin/migrate.sh' release_command = '/app/bin/migrate.sh'
[env] [env]
PHX_HOST = 'wanderer-test.fly.dev' PHX_HOST = 'wanderer.fly.dev'
PHX_SERVER = 'true' PHX_SERVER = 'true'
PORT = '8080' PORT = '8080'
[metrics]
port = 4021
path = "/metrics"
[http_service] [http_service]
internal_port = 8080 internal_port = 8080
force_https = true force_https = true
auto_stop_machines = 'off' auto_stop_machines = false
auto_start_machines = false auto_start_machines = false
min_machines_running = 0 min_machines_running = 0
processes = ['app'] processes = ['app']
@@ -32,9 +35,6 @@ swap_size_mb = 512
soft_limit = 1000 soft_limit = 1000
[[vm]] [[vm]]
size = 'shared-cpu-1x' memory = '1gb'
cpu_kind = 'shared'
[[metrics]] cpus = 1
port = 4021
path = '/metrics'
https = false

View File

@@ -4,6 +4,8 @@ defmodule WandererApp.Api.Calculations.CalcMapPermissions do
use Ash.Resource.Calculation use Ash.Resource.Calculation
require Ash.Query require Ash.Query
import Bitwise
@impl true @impl true
def load(_query, _opts, _context) do def load(_query, _opts, _context) do
[ [
@@ -15,8 +17,116 @@ defmodule WandererApp.Api.Calculations.CalcMapPermissions do
end end
@impl true @impl true
def calculate([record], _opts, %{actor: actor}), def calculate([record], _opts, %{actor: actor}) do
do: WandererApp.Permissions.check_characters_access(actor.characters, record.acls) characters = actor.characters
character_ids = characters |> Enum.map(& &1.id)
character_eve_ids = characters |> Enum.map(& &1.eve_id)
character_corporation_ids =
characters |> Enum.map(& &1.corporation_id) |> Enum.map(&to_string/1)
character_alliance_ids = characters |> Enum.map(& &1.alliance_id) |> Enum.map(&to_string/1)
result =
record.acls
|> Enum.reduce([0, 0], fn acl, acc ->
is_owner? = acl.owner_id in character_ids
is_character_member? =
acl.members |> Enum.any?(fn member -> member.eve_character_id in character_eve_ids end)
is_corporation_member? =
acl.members
|> Enum.any?(fn member -> member.eve_corporation_id in character_corporation_ids end)
is_alliance_member? =
acl.members
|> Enum.any?(fn member -> member.eve_alliance_id in character_alliance_ids end)
if is_owner? || is_character_member? || is_corporation_member? || is_alliance_member? do
case acc do
[_, -1] ->
[-1, -1]
[-1, char_acc] ->
char_acl_mask =
acl.members
|> Enum.filter(fn member ->
member.eve_character_id in character_eve_ids
end)
|> Enum.reduce(0, fn member, acc ->
case acc do
-1 -> -1
_ -> WandererApp.Permissions.calc_role_mask(member.role, acc)
end
end)
char_acc =
case char_acl_mask do
-1 -> -1
_ -> char_acc ||| char_acl_mask
end
[-1, char_acc]
[any_acc, char_acc] ->
any_acl_mask =
acl.members
|> Enum.filter(fn member ->
member.eve_character_id in character_eve_ids ||
member.eve_corporation_id in character_corporation_ids ||
member.eve_alliance_id in character_alliance_ids
end)
|> Enum.reduce(0, fn member, acc ->
case acc do
-1 -> -1
_ -> WandererApp.Permissions.calc_role_mask(member.role, acc)
end
end)
char_acl_mask =
acl.members
|> Enum.filter(fn member ->
member.eve_character_id in character_eve_ids
end)
|> Enum.reduce(0, fn member, acc ->
case acc do
-1 -> -1
_ -> WandererApp.Permissions.calc_role_mask(member.role, acc)
end
end)
any_acc =
case any_acl_mask do
-1 -> -1
_ -> any_acc ||| any_acl_mask
end
char_acc =
case char_acl_mask do
-1 -> -1
_ -> char_acc ||| char_acl_mask
end
[any_acc, char_acc]
end
else
acc
end
end)
case result do
[_, -1] ->
[-1]
[-1, char_acc] ->
[char_acc]
[any_acc, _char_acc] ->
[any_acc]
end
end
@impl true @impl true
def calculate(_records, _opts, _context) do def calculate(_records, _opts, _context) do

View File

@@ -17,12 +17,12 @@ defmodule WandererApp.Api.MapCharacterSettings do
action: :read_by_map action: :read_by_map
) )
define(:tracked_by_map_filtered, define(:tracked_by_map,
action: :tracked_by_map_filtered action: :tracked_by_map
) )
define(:tracked_by_map_all, define(:tracked_by_map_all,
action: :tracked_by_map_all action: :read_tracked_by_map
) )
define(:track, action: :track) define(:track, action: :track)
@@ -38,7 +38,7 @@ defmodule WandererApp.Api.MapCharacterSettings do
defaults [:create, :read, :update, :destroy] defaults [:create, :read, :update, :destroy]
read :tracked_by_map_filtered do read :tracked_by_map do
argument(:map_id, :string, allow_nil?: false) argument(:map_id, :string, allow_nil?: false)
argument(:character_ids, {:array, :uuid}, allow_nil?: false) argument(:character_ids, {:array, :uuid}, allow_nil?: false)
@@ -52,7 +52,7 @@ defmodule WandererApp.Api.MapCharacterSettings do
filter(expr(map_id == ^arg(:map_id))) filter(expr(map_id == ^arg(:map_id)))
end end
read :tracked_by_map_all do read :read_tracked_by_map do
argument(:map_id, :string, allow_nil?: false) argument(:map_id, :string, allow_nil?: false)
filter(expr(map_id == ^arg(:map_id) and tracked == true)) filter(expr(map_id == ^arg(:map_id) and tracked == true))
end end

View File

@@ -16,7 +16,6 @@ defmodule WandererApp.Api.MapSystemSignature do
define(:update, action: :update) define(:update, action: :update)
define(:update_linked_system, action: :update_linked_system) define(:update_linked_system, action: :update_linked_system)
define(:update_type, action: :update_type) define(:update_type, action: :update_type)
define(:update_group, action: :update_group)
define(:by_id, define(:by_id,
get_by: [:id], get_by: [:id],
@@ -87,10 +86,6 @@ defmodule WandererApp.Api.MapSystemSignature do
accept [:type] accept [:type]
end end
update :update_group do
accept [:group]
end
read :by_system_id do read :by_system_id do
argument(:system_id, :string, allow_nil?: false) argument(:system_id, :string, allow_nil?: false)

View File

@@ -3,8 +3,6 @@ defmodule WandererApp.Application do
use Application use Application
require Logger
@impl true @impl true
def start(_type, _args) do def start(_type, _args) do
children = children =
@@ -47,16 +45,7 @@ defmodule WandererApp.Application do
# See https://hexdocs.pm/elixir/Supervisor.html # See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options # for other strategies and supported options
opts = [strategy: :one_for_one, name: WandererApp.Supervisor] opts = [strategy: :one_for_one, name: WandererApp.Supervisor]
Supervisor.start_link(children, opts) Supervisor.start_link(children, opts)
|> case do
{:ok, _pid} = ok ->
ok
{:error, info} = e ->
Logger.error("Failed to start application: #{inspect(info)}")
e
end
end end
# Tell Phoenix to update the endpoint configuration # Tell Phoenix to update the endpoint configuration

View File

@@ -37,32 +37,32 @@ defmodule WandererApp.Esi.ApiClient do
@logger Application.compile_env(:wanderer_app, :logger) @logger Application.compile_env(:wanderer_app, :logger)
def get_server_status, do: get("/status") def get_server_status, do: _get("/status")
def set_autopilot_waypoint(add_to_beginning, clear_other_waypoints, destination_id, opts \\ []), def set_autopilot_waypoint(add_to_beginning, clear_other_waypoints, destination_id, opts \\ []) do
do: _post_esi(
post_esi( "/ui/autopilot/waypoint",
"/ui/autopilot/waypoint", opts
opts |> Keyword.merge(
|> Keyword.merge( params: %{
params: %{ add_to_beginning: add_to_beginning,
add_to_beginning: add_to_beginning, clear_other_waypoints: clear_other_waypoints,
clear_other_waypoints: clear_other_waypoints, destination_id: destination_id
destination_id: destination_id }
}
)
) )
)
end
def post_characters_affiliation(character_eve_ids, _opts) def post_characters_affiliation(character_eve_ids, _opts)
when is_list(character_eve_ids), when is_list(character_eve_ids) do
do: _post(
post( "#{@base_url}/characters/affiliation/",
"#{@base_url}/characters/affiliation/", json: character_eve_ids,
json: character_eve_ids, params: %{
params: %{ datasource: "tranquility"
datasource: "tranquility" }
} )
) end
def find_routes(map_id, origin, hubs, routes_settings) do def find_routes(map_id, origin, hubs, routes_settings) do
origin = origin |> String.to_integer() origin = origin |> String.to_integer()
@@ -184,7 +184,7 @@ defmodule WandererApp.Esi.ApiClient do
routes = routes =
all_routes all_routes
|> Enum.map(fn route_info -> |> Enum.map(fn route_info ->
map_route_info(route_info) _map_route_info(route_info)
end) end)
|> Enum.filter(fn route_info -> not is_nil(route_info) end) |> Enum.filter(fn route_info -> not is_nil(route_info) end)
@@ -200,7 +200,7 @@ defmodule WandererApp.Esi.ApiClient do
{:ok, result} {:ok, result}
_ -> _ ->
case get_all_routes_custom(hubs, origin, params) do case _get_all_routes_custom(hubs, origin, params) do
{:ok, result} -> {:ok, result} ->
WandererApp.Cache.insert( WandererApp.Cache.insert(
cache_key, cache_key,
@@ -210,21 +210,22 @@ defmodule WandererApp.Esi.ApiClient do
{:ok, result} {:ok, result}
{:error, _error} -> {:error, error} ->
@logger.error("Error getting custom routes for #{inspect(origin)}: #{inspect(error)}")
@logger.error("Error getting custom routes for #{inspect(origin)}: #{inspect(hubs)}") @logger.error("Error getting custom routes for #{inspect(origin)}: #{inspect(hubs)}")
@logger.error( @logger.error(
"Error getting custom routes for #{inspect(origin)}: #{inspect(params)}" "Error getting custom routes for #{inspect(origin)}: #{inspect(params)}"
) )
get_all_routes_eve(hubs, origin, params, opts) _get_all_routes_eve(hubs, origin, params, opts)
end end
end end
end end
defp get_all_routes_custom(hubs, origin, params), defp _get_all_routes_custom(hubs, origin, params),
do: do:
post( _post(
"#{get_custom_route_base_url()}/route/multiple", "#{get_custom_route_base_url()}/route/multiple",
[ [
json: %{ json: %{
@@ -238,7 +239,7 @@ defmodule WandererApp.Esi.ApiClient do
|> Keyword.merge(@timeout_opts) |> Keyword.merge(@timeout_opts)
) )
def get_all_routes_eve(hubs, origin, params, opts), def _get_all_routes_eve(hubs, origin, params, opts),
do: do:
{:ok, {:ok,
hubs hubs
@@ -307,7 +308,7 @@ defmodule WandererApp.Esi.ApiClient do
opts: [ttl: @ttl] opts: [ttl: @ttl]
) )
def get_character_info(eve_id, opts \\ []) do def get_character_info(eve_id, opts \\ []) do
case get( case _get(
"/characters/#{eve_id}/", "/characters/#{eve_id}/",
opts |> _with_cache_opts() opts |> _with_cache_opts()
) do ) do
@@ -384,7 +385,7 @@ defmodule WandererApp.Esi.ApiClient do
defp _get_routes_eve(origin, destination, params, opts), defp _get_routes_eve(origin, destination, params, opts),
do: do:
get( _get(
"/route/#{origin}/#{destination}/?#{params |> Plug.Conn.Query.encode()}", "/route/#{origin}/#{destination}/?#{params |> Plug.Conn.Query.encode()}",
opts |> _with_cache_opts() opts |> _with_cache_opts()
) )
@@ -393,14 +394,14 @@ defmodule WandererApp.Esi.ApiClient do
defp _get_alliance_info(alliance_eve_id, info_path, opts), defp _get_alliance_info(alliance_eve_id, info_path, opts),
do: do:
get( _get(
"/alliances/#{alliance_eve_id}/#{info_path}", "/alliances/#{alliance_eve_id}/#{info_path}",
opts |> _with_cache_opts() opts |> _with_cache_opts()
) )
defp _get_corporation_info(corporation_eve_id, info_path, opts), defp _get_corporation_info(corporation_eve_id, info_path, opts),
do: do:
get( _get(
"/corporations/#{corporation_eve_id}/#{info_path}", "/corporations/#{corporation_eve_id}/#{info_path}",
opts |> _with_cache_opts() opts |> _with_cache_opts()
) )
@@ -415,7 +416,7 @@ defmodule WandererApp.Esi.ApiClient do
character_id = opts |> Keyword.get(:character_id, nil) character_id = opts |> Keyword.get(:character_id, nil)
if not _is_access_token_expired?(character_id) do if not _is_access_token_expired?(character_id) do
get( _get(
path, path,
auth_opts, auth_opts,
opts opts
@@ -436,7 +437,7 @@ defmodule WandererApp.Esi.ApiClient do
defp _get_corporation_auth_data(corporation_eve_id, info_path, opts), defp _get_corporation_auth_data(corporation_eve_id, info_path, opts),
do: do:
get( _get(
"/corporations/#{corporation_eve_id}/#{info_path}", "/corporations/#{corporation_eve_id}/#{info_path}",
[params: opts[:params] || []] ++ [params: opts[:params] || []] ++
(opts |> _get_auth_opts() |> _with_cache_opts()), (opts |> _get_auth_opts() |> _with_cache_opts()),
@@ -447,14 +448,14 @@ defmodule WandererApp.Esi.ApiClient do
opts |> Keyword.merge(@cache_opts) |> Keyword.merge(cache_dir: System.tmp_dir!()) opts |> Keyword.merge(@cache_opts) |> Keyword.merge(cache_dir: System.tmp_dir!())
end end
defp post_esi(path, opts), defp _post_esi(path, opts),
do: do:
post( _post(
"#{@base_url}#{path}", "#{@base_url}#{path}",
[params: opts[:params] || []] ++ (opts |> _get_auth_opts()) [params: opts[:params] || []] ++ (opts |> _get_auth_opts())
) )
defp get(path, api_opts \\ [], opts \\ []) do defp _get(path, api_opts \\ [], opts \\ []) do
try do try do
case Req.get("#{@base_url}#{path}", api_opts |> Keyword.merge(@retry_opts)) do case Req.get("#{@base_url}#{path}", api_opts |> Keyword.merge(@retry_opts)) do
{:ok, %{status: 200, body: body}} -> {:ok, %{status: 200, body: body}} ->
@@ -486,7 +487,7 @@ defmodule WandererApp.Esi.ApiClient do
end end
end end
defp post(url, opts) do defp _post(url, opts) do
try do try do
case Req.post("#{url}", opts) do case Req.post("#{url}", opts) do
{:ok, %{status: status, body: body}} when status in [200, 201] -> {:ok, %{status: status, body: body}} when status in [200, 201] ->
@@ -524,7 +525,7 @@ defmodule WandererApp.Esi.ApiClient do
{:ok, token} -> {:ok, token} ->
auth_opts = [access_token: token.access_token] |> _get_auth_opts() auth_opts = [access_token: token.access_token] |> _get_auth_opts()
get( _get(
path, path,
api_opts |> Keyword.merge(auth_opts), api_opts |> Keyword.merge(auth_opts),
opts |> Keyword.merge(retry_count: retry_count + 1) opts |> Keyword.merge(retry_count: retry_count + 1)
@@ -599,7 +600,7 @@ defmodule WandererApp.Esi.ApiClient do
end end
end end
defp map_route_info( defp _map_route_info(
%{ %{
"origin" => origin, "origin" => origin,
"destination" => destination, "destination" => destination,
@@ -608,14 +609,14 @@ defmodule WandererApp.Esi.ApiClient do
} = _route_info } = _route_info
), ),
do: do:
map_route_info(%{ _map_route_info(%{
origin: origin, origin: origin,
destination: destination, destination: destination,
systems: result_systems, systems: result_systems,
success: success success: success
}) })
defp map_route_info( defp _map_route_info(
%{origin: origin, destination: destination, systems: result_systems, success: success} = %{origin: origin, destination: destination, systems: result_systems, success: success} =
_route_info _route_info
) do ) do
@@ -637,5 +638,5 @@ defmodule WandererApp.Esi.ApiClient do
} }
end end
defp map_route_info(_), do: nil defp _map_route_info(_), do: nil
end end

View File

@@ -8,7 +8,6 @@ defmodule WandererApp.Map do
defstruct map_id: nil, defstruct map_id: nil,
name: nil, name: nil,
scope: :none, scope: :none,
owner_id: nil,
characters: [], characters: [],
systems: Map.new(), systems: Map.new(),
hubs: [], hubs: [],
@@ -17,12 +16,11 @@ defmodule WandererApp.Map do
characters_limit: nil, characters_limit: nil,
hubs_limit: nil hubs_limit: nil
def new(%{id: map_id, name: name, scope: scope, owner_id: owner_id, acls: acls, hubs: hubs}) do def new(%{id: map_id, name: name, scope: scope, acls: acls, hubs: hubs}) do
map = map =
struct!(__MODULE__, struct!(__MODULE__,
map_id: map_id, map_id: map_id,
scope: scope, scope: scope,
owner_id: owner_id,
name: name, name: name,
acls: acls, acls: acls,
hubs: hubs hubs: hubs
@@ -216,7 +214,7 @@ defmodule WandererApp.Map do
%{visible: true} = system -> %{visible: true} = system ->
system system
_system -> _ ->
nil nil
end end
end end
@@ -264,7 +262,7 @@ defmodule WandererApp.Map do
case not Map.has_key?(systems, solar_system_id) do case not Map.has_key?(systems, solar_system_id) do
true -> true ->
map_id map_id
|> update_map(%{systems: Map.put(systems, solar_system_id, system)}) |> update_map(%{systems: Map.put_new(systems, solar_system_id, system)})
:ok :ok

View File

@@ -183,12 +183,6 @@ defmodule WandererApp.Map.Server do
|> map_pid! |> map_pid!
|> GenServer.cast({&Impl.delete_connection/2, [connection_info]}) |> GenServer.cast({&Impl.delete_connection/2, [connection_info]})
def get_connection_info(map_id, connection_info) when is_binary(map_id),
do:
map_id
|> map_pid!
|> GenServer.call({&Impl.get_connection_info/2, [connection_info]}, :timer.minutes(1))
def update_connection_time_status(map_id, connection_info) when is_binary(map_id), def update_connection_time_status(map_id, connection_info) when is_binary(map_id),
do: do:
map_id map_id

View File

@@ -77,7 +77,6 @@ defmodule WandererApp.Map.Server.Impl do
# @unknown 100_100 # @unknown 100_100
@systems_cleanup_timeout :timer.minutes(30) @systems_cleanup_timeout :timer.minutes(30)
@characters_cleanup_timeout :timer.minutes(1)
@connections_cleanup_timeout :timer.minutes(2) @connections_cleanup_timeout :timer.minutes(2)
@connection_time_status_eol 1 @connection_time_status_eol 1
@@ -113,28 +112,20 @@ defmodule WandererApp.Map.Server.Impl do
end end
def load_state(%__MODULE__{map_id: map_id} = state) do def load_state(%__MODULE__{map_id: map_id} = state) do
with {:ok, map} <- with {:ok, map} <- WandererApp.MapRepo.get(map_id, [:acls, :characters]),
WandererApp.MapRepo.get(map_id, [
:owner,
:characters,
acls: [
:owner_id,
members: [:role, :eve_character_id, :eve_corporation_id, :eve_alliance_id]
]
]),
{:ok, systems} <- WandererApp.MapSystemRepo.get_visible_by_map(map_id), {:ok, systems} <- WandererApp.MapSystemRepo.get_visible_by_map(map_id),
{:ok, connections} <- WandererApp.MapConnectionRepo.get_by_map(map_id), {:ok, connections} <- WandererApp.MapConnectionRepo.get_by_map(map_id),
{:ok, subscription_settings} <- {:ok, subscription_settings} <-
WandererApp.Map.SubscriptionManager.get_active_map_subscription(map_id) do WandererApp.Map.SubscriptionManager.get_active_map_subscription(map_id) do
state state
|> init_map( |> _init_map(
map, map,
subscription_settings, subscription_settings,
systems, systems,
connections connections
) )
|> init_map_systems(systems) |> _init_map_systems(systems)
|> init_map_cache() |> _init_map_cache()
else else
error -> error ->
@logger.error("Failed to load map state: #{inspect(error, pretty: true)}") @logger.error("Failed to load map state: #{inspect(error, pretty: true)}")
@@ -143,7 +134,7 @@ defmodule WandererApp.Map.Server.Impl do
end end
def start_map(%__MODULE__{map: map, map_id: map_id} = state) do def start_map(%__MODULE__{map: map, map_id: map_id} = state) do
with :ok <- track_acls(map.acls |> Enum.map(& &1.id)) do with :ok <- _track_acls(map.acls |> Enum.map(& &1.id)) do
@pubsub_client.subscribe( @pubsub_client.subscribe(
WandererApp.PubSub, WandererApp.PubSub,
"maps:#{map_id}" "maps:#{map_id}"
@@ -153,8 +144,7 @@ defmodule WandererApp.Map.Server.Impl do
Process.send_after(self(), :update_tracked_characters, 100) Process.send_after(self(), :update_tracked_characters, 100)
Process.send_after(self(), :update_presence, @update_presence_timeout) Process.send_after(self(), :update_presence, @update_presence_timeout)
Process.send_after(self(), :cleanup_connections, 5000) Process.send_after(self(), :cleanup_connections, 5000)
Process.send_after(self(), :cleanup_systems, 10_000) Process.send_after(self(), :cleanup_systems, 10000)
Process.send_after(self(), :cleanup_characters, :timer.minutes(5))
Process.send_after(self(), :backup_state, @backup_state_timeout) Process.send_after(self(), :backup_state, @backup_state_timeout)
WandererApp.Cache.insert("map_#{map_id}:started", true) WandererApp.Cache.insert("map_#{map_id}:started", true)
@@ -179,7 +169,7 @@ defmodule WandererApp.Map.Server.Impl do
:telemetry.execute([:wanderer_app, :map, :stopped], %{count: 1}) :telemetry.execute([:wanderer_app, :map, :stopped], %{count: 1})
state state
|> maybe_stop_rtree() |> _maybe_stop_rtree()
end end
def get_map(%{map: map} = _state), do: {:ok, map} def get_map(%{map: map} = _state), do: {:ok, map}
@@ -259,37 +249,37 @@ defmodule WandererApp.Map.Server.Impl do
state, state,
update update
), ),
do: state |> update_system(:update_name, [:name], update) do: state |> _update_system(:update_name, [:name], update)
def update_system_description( def update_system_description(
state, state,
update update
), ),
do: state |> update_system(:update_description, [:description], update) do: state |> _update_system(:update_description, [:description], update)
def update_system_status( def update_system_status(
state, state,
update update
), ),
do: state |> update_system(:update_status, [:status], update) do: state |> _update_system(:update_status, [:status], update)
def update_system_tag( def update_system_tag(
state, state,
update update
), ),
do: state |> update_system(:update_tag, [:tag], update) do: state |> _update_system(:update_tag, [:tag], update)
def update_system_locked( def update_system_locked(
state, state,
update update
), ),
do: state |> update_system(:update_locked, [:locked], update) do: state |> _update_system(:update_locked, [:locked], update)
def update_system_labels( def update_system_labels(
state, state,
update update
), ),
do: state |> update_system(:update_labels, [:labels], update) do: state |> _update_system(:update_labels, [:labels], update)
def update_system_position( def update_system_position(
%{rtree_name: rtree_name} = state, %{rtree_name: rtree_name} = state,
@@ -297,7 +287,7 @@ defmodule WandererApp.Map.Server.Impl do
), ),
do: do:
state state
|> update_system( |> _update_system(
:update_position, :update_position,
[:position_x, :position_y], [:position_x, :position_y],
update, update,
@@ -445,34 +435,12 @@ defmodule WandererApp.Map.Server.Impl do
state state
end end
def get_connection_info(
%{map_id: map_id} = _state,
%{
solar_system_source_id: solar_system_source_id,
solar_system_target_id: solar_system_target_id
} = _connection_info
) do
WandererApp.Map.find_connection(
map_id,
solar_system_source_id,
solar_system_target_id
)
|> case do
{:ok, %{id: connection_id}} ->
connection_mark_eol_time = get_connection_mark_eol_time(map_id, connection_id, nil)
{:ok, %{marl_eol_time: connection_mark_eol_time}}
_ ->
{:error, :not_found}
end
end
def update_connection_time_status( def update_connection_time_status(
%{map_id: map_id} = state, %{map_id: map_id} = state,
connection_update connection_update
), ),
do: do:
update_connection(state, :update_time_status, [:time_status], connection_update, fn _update_connection(state, :update_time_status, [:time_status], connection_update, fn
%{id: connection_id, time_status: time_status} -> %{id: connection_id, time_status: time_status} ->
case time_status == @connection_time_status_eol do case time_status == @connection_time_status_eol do
true -> true ->
@@ -491,25 +459,25 @@ defmodule WandererApp.Map.Server.Impl do
state, state,
connection_update connection_update
), ),
do: update_connection(state, :update_mass_status, [:mass_status], connection_update) do: _update_connection(state, :update_mass_status, [:mass_status], connection_update)
def update_connection_ship_size_type( def update_connection_ship_size_type(
state, state,
connection_update connection_update
), ),
do: update_connection(state, :update_ship_size_type, [:ship_size_type], connection_update) do: _update_connection(state, :update_ship_size_type, [:ship_size_type], connection_update)
def update_connection_locked( def update_connection_locked(
state, state,
connection_update connection_update
), ),
do: update_connection(state, :update_locked, [:locked], connection_update) do: _update_connection(state, :update_locked, [:locked], connection_update)
def update_connection_custom_info( def update_connection_custom_info(
state, state,
connection_update connection_update
), ),
do: update_connection(state, :update_custom_info, [:custom_info], connection_update) do: _update_connection(state, :update_custom_info, [:custom_info], connection_update)
def import_settings(%{map_id: map_id} = state, settings, user_id) do def import_settings(%{map_id: map_id} = state, settings, user_id) do
WandererApp.Cache.put( WandererApp.Cache.put(
@@ -519,9 +487,9 @@ defmodule WandererApp.Map.Server.Impl do
state = state =
state state
|> maybe_import_systems(settings, user_id, nil) |> _maybe_import_systems(settings, user_id, nil)
|> maybe_import_connections(settings, user_id) |> _maybe_import_connections(settings, user_id)
|> maybe_import_hubs(settings, user_id) |> _maybe_import_hubs(settings, user_id)
WandererApp.Cache.take("map_#{map_id}:importing") WandererApp.Cache.take("map_#{map_id}:importing")
@@ -541,11 +509,11 @@ defmodule WandererApp.Map.Server.Impl do
|> Enum.map(fn character_id -> |> Enum.map(fn character_id ->
Task.start_link(fn -> Task.start_link(fn ->
character_updates = character_updates =
maybe_update_online(map_id, character_id) ++ _maybe_update_online(map_id, character_id) ++
maybe_update_location(map_id, character_id) ++ _maybe_update_location(map_id, character_id) ++
maybe_update_ship(map_id, character_id) ++ _maybe_update_ship(map_id, character_id) ++
maybe_update_alliance(map_id, character_id) ++ _maybe_update_alliance(map_id, character_id) ++
maybe_update_corporation(map_id, character_id) _maybe_update_corporation(map_id, character_id)
character_updates character_updates
|> Enum.filter(fn update -> update != :skip end) |> Enum.filter(fn update -> update != :skip end)
@@ -553,7 +521,7 @@ defmodule WandererApp.Map.Server.Impl do
update update
|> case do |> case do
{:character_location, location_info, old_location_info} -> {:character_location, location_info, old_location_info} ->
update_location( _update_location(
character_id, character_id,
location_info, location_info,
old_location_info, old_location_info,
@@ -569,25 +537,9 @@ defmodule WandererApp.Map.Server.Impl do
:broadcast :broadcast
{:character_alliance, _info} -> {:character_alliance, _info} ->
WandererApp.Cache.insert_or_update(
"map_#{map_id}:invalidate_character_ids",
[character_id],
fn ids ->
[character_id | ids]
end
)
:broadcast :broadcast
{:character_corporation, _info} -> {:character_corporation, _info} ->
WandererApp.Cache.insert_or_update(
"map_#{map_id}:invalidate_character_ids",
[character_id],
fn ids ->
[character_id | ids]
end
)
:broadcast :broadcast
_ -> _ ->
@@ -617,21 +569,17 @@ defmodule WandererApp.Map.Server.Impl do
Process.send_after(self(), :update_tracked_characters, @update_tracked_characters_timeout) Process.send_after(self(), :update_tracked_characters, @update_tracked_characters_timeout)
Task.start_link(fn -> Task.start_link(fn ->
{:ok, map_tracked_character_ids} = map_characters =
map_id map_id
|> WandererApp.MapCharacterSettingsRepo.get_tracked_by_map_all() |> WandererApp.Map.get_map!()
|> case do |> Map.get(:characters, [])
{:ok, settings} -> {:ok, settings |> Enum.map(&Map.get(&1, :character_id))}
_ -> {:ok, []}
end
{:ok, tracked_characters} = WandererApp.Cache.lookup("tracked_characters", []) {:ok, tracked_characters} = WandererApp.Cache.lookup("tracked_characters", [])
map_active_tracked_characters = map_tracked_characters =
map_tracked_character_ids map_characters |> Enum.filter(fn character -> character in tracked_characters end)
|> Enum.filter(fn character -> character in tracked_characters end)
WandererApp.Cache.insert("maps:#{map_id}:tracked_characters", map_active_tracked_characters) WandererApp.Cache.insert("maps:#{map_id}:tracked_characters", map_tracked_characters)
:ok :ok
end) end)
@@ -642,37 +590,29 @@ defmodule WandererApp.Map.Server.Impl do
def handle_event(:update_presence, %{map_id: map_id} = state) do def handle_event(:update_presence, %{map_id: map_id} = state) do
Process.send_after(self(), :update_presence, @update_presence_timeout) Process.send_after(self(), :update_presence, @update_presence_timeout)
update_presence(map_id) _update_presence(map_id)
state state
end end
def handle_event(:backup_state, state) do def handle_event(:backup_state, state) do
Process.send_after(self(), :backup_state, @backup_state_timeout) Process.send_after(self(), :backup_state, @backup_state_timeout)
{:ok, _map_state} = state |> save_map_state() {:ok, _map_state} = state |> _save_map_state()
state state
end end
def handle_event( def handle_event({:map_acl_updated, added_acls, removed_acls}, %{map: old_map} = state) do
{:map_acl_updated, added_acls, removed_acls}, {:ok, map} = WandererApp.MapRepo.get(old_map.map_id, [:acls])
%{map_id: map_id, map: old_map} = state
) do
{:ok, map} =
WandererApp.MapRepo.get(map_id,
acls: [
:owner_id,
members: [:role, :eve_character_id, :eve_corporation_id, :eve_alliance_id]
]
)
track_acls(added_acls) _track_acls(added_acls)
result = result =
(added_acls ++ removed_acls) [added_acls | removed_acls]
|> List.flatten()
|> Task.async_stream( |> Task.async_stream(
fn acl_id -> fn acl_id ->
update_acl(acl_id) _update_acl(acl_id)
end, end,
max_concurrency: 10, max_concurrency: 10,
timeout: :timer.seconds(15) timeout: :timer.seconds(15)
@@ -701,45 +641,29 @@ defmodule WandererApp.Map.Server.Impl do
} }
error -> error ->
@logger.error("Failed to update map #{map_id} acl: #{inspect(error, pretty: true)}") @logger.error(
"Failed to update map #{old_map.map_id} acl: #{inspect(error, pretty: true)}"
)
acc acc
end end
end end
) )
map_update = %{acls: map.acls, scope: map.scope} _broadcast_acl_updates({:ok, result})
WandererApp.Map.update_map(map_id, map_update) %{state | map: %{old_map | acls: map.acls, scope: map.scope}}
broadcast_acl_updates({:ok, result}, map_id)
%{state | map: Map.merge(old_map, map_update)}
end end
def handle_event({:acl_updated, %{acl_id: acl_id}}, %{map_id: map_id, map: old_map} = state) do def handle_event({:acl_updated, %{acl_id: acl_id}}, %{map: map} = state) do
{:ok, map} =
WandererApp.MapRepo.get(map_id,
acls: [
:owner_id,
members: [:role, :eve_character_id, :eve_corporation_id, :eve_alliance_id]
]
)
if map.acls |> Enum.map(& &1.id) |> Enum.member?(acl_id) do if map.acls |> Enum.map(& &1.id) |> Enum.member?(acl_id) do
map_update = %{acls: map.acls}
WandererApp.Map.update_map(map_id, map_update)
:ok = :ok =
acl_id acl_id
|> update_acl() |> _update_acl()
|> broadcast_acl_updates(map_id) |> _broadcast_acl_updates()
state
else
state
end end
state
end end
def handle_event(:cleanup_connections, %{map_id: map_id} = state) do def handle_event(:cleanup_connections, %{map_id: map_id} = state) do
@@ -755,7 +679,7 @@ defmodule WandererApp.Map.Server.Impl do
} -> } ->
DateTime.diff(DateTime.utc_now(), inserted_at, :hour) >= DateTime.diff(DateTime.utc_now(), inserted_at, :hour) >=
@connection_auto_eol_hours and @connection_auto_eol_hours and
is_connection_valid( _is_connection_valid(
:wormholes, :wormholes,
solar_system_source_id, solar_system_source_id,
solar_system_target_id solar_system_target_id
@@ -783,7 +707,7 @@ defmodule WandererApp.Map.Server.Impl do
solar_system_source: solar_system_source_id, solar_system_source: solar_system_source_id,
solar_system_target: solar_system_target_id solar_system_target: solar_system_target_id
} -> } ->
connection_mark_eol_time = get_connection_mark_eol_time(map_id, connection_id) connection_mark_eol_time = _get_connection_mark_eol_time(map_id, connection_id)
reverse_connection = reverse_connection =
WandererApp.Map.get_connection( WandererApp.Map.get_connection(
@@ -793,23 +717,24 @@ defmodule WandererApp.Map.Server.Impl do
) )
is_connection_exist = is_connection_exist =
is_connection_exist( _is_connection_exist(
map_id, map_id,
solar_system_source_id, solar_system_source_id,
solar_system_target_id solar_system_target_id
) || not is_nil(reverse_connection) )
is_connection_valid = is_connection_valid =
is_connection_valid( _is_connection_valid(
:wormholes, :wormholes,
solar_system_source_id, solar_system_source_id,
solar_system_target_id solar_system_target_id
) )
not is_connection_exist || not is_connection_exist or
(is_connection_valid && not is_nil(reverse_connection) or
(is_connection_valid and
(DateTime.diff(DateTime.utc_now(), inserted_at, :hour) >= (DateTime.diff(DateTime.utc_now(), inserted_at, :hour) >=
@connection_auto_expire_hours || @connection_auto_expire_hours or
DateTime.diff(DateTime.utc_now(), connection_mark_eol_time, :hour) >= DateTime.diff(DateTime.utc_now(), connection_mark_eol_time, :hour) >=
@connection_auto_expire_hours - @connection_auto_eol_hours)) @connection_auto_expire_hours - @connection_auto_eol_hours))
end) end)
@@ -828,76 +753,6 @@ defmodule WandererApp.Map.Server.Impl do
state state
end end
def handle_event(:cleanup_characters, %{map_id: map_id, map: %{owner_id: owner_id}} = state) do
Process.send_after(self(), :cleanup_characters, @characters_cleanup_timeout)
{:ok, invalidate_character_ids} =
WandererApp.Cache.lookup(
"map_#{map_id}:invalidate_character_ids",
[]
)
invalidate_character_ids
|> Task.async_stream(
fn character_id ->
character_id
|> WandererApp.Character.get_character()
|> case do
{:ok, character} ->
acls =
map_id
|> WandererApp.Map.get_map!()
|> Map.get(:acls, [])
[character_permissions] =
WandererApp.Permissions.check_characters_access([character], acls)
map_permissions =
WandererApp.Permissions.get_map_permissions(
character_permissions,
owner_id,
[character_id]
)
case map_permissions do
%{view_system: false} ->
{:remove_character, character_id}
%{track_character: false} ->
{:remove_character, character_id}
_ ->
:ok
end
_ ->
:ok
end
end,
timeout: :timer.seconds(60),
max_concurrency: System.schedulers_online(),
on_timeout: :kill_task
)
|> Enum.each(fn
{:ok, {:remove_character, character_id}} ->
state |> remove_and_untrack_characters([character_id])
:ok
{:ok, _result} ->
:ok
{:error, reason} ->
@logger.error("Error in cleanup_characters: #{inspect(reason)}")
end)
WandererApp.Cache.insert(
"map_#{map_id}:invalidate_character_ids",
[]
)
state
end
def handle_event(:cleanup_systems, %{map_id: map_id} = state) do def handle_event(:cleanup_systems, %{map_id: map_id} = state) do
Process.send_after(self(), :cleanup_systems, @systems_cleanup_timeout) Process.send_after(self(), :cleanup_systems, @systems_cleanup_timeout)
@@ -957,9 +812,8 @@ defmodule WandererApp.Map.Server.Impl do
do: %{ do: %{
state state
| map_opts: [ | map_opts: [
layout: options |> Map.get("layout", "left_to_right"), layout: options |> Map.get("layout"),
store_custom_labels: store_custom_labels: options |> Map.get("store_custom_labels")
options |> Map.get("store_custom_labels", "false") |> String.to_existing_atom()
] ]
} }
@@ -970,71 +824,47 @@ defmodule WandererApp.Map.Server.Impl do
end end
def handle_event(msg, state) do def handle_event(msg, state) do
Logger.warning("Unhandled event: #{inspect(msg)}") @logger.warning("Unhandled event: #{inspect(msg)}")
state state
end end
def broadcast!(map_id, event, payload \\ nil) do def broadcast!(map_id, event, payload \\ nil) do
if can_broadcast?(map_id) do if _can_broadcast?(map_id) do
@pubsub_client.broadcast!(WandererApp.PubSub, map_id, %{event: event, payload: payload}) @pubsub_client.broadcast!(WandererApp.PubSub, map_id, %{event: event, payload: payload})
end end
:ok :ok
end end
defp remove_and_untrack_characters(%{map_id: map_id} = state, character_ids) do defp _get_connection_mark_eol_time(map_id, connection_id) do
Logger.warning(fn -> case WandererApp.Cache.get("map_#{map_id}:conn_#{connection_id}:mark_eol_time") do
"Map #{map_id} - remove and untrack characters #{inspect(character_ids)}"
end)
map_id
|> _untrack_characters(character_ids)
map_id
|> WandererApp.MapCharacterSettingsRepo.get_tracked_by_map_filtered(character_ids)
|> case do
{:ok, settings} ->
settings
|> Enum.each(fn s ->
s |> WandererApp.MapCharacterSettingsRepo.untrack()
state |> remove_character(s.character_id)
end)
_ ->
:ok
end
end
defp get_connection_mark_eol_time(map_id, connection_id, default \\ DateTime.utc_now()) do
WandererApp.Cache.get("map_#{map_id}:conn_#{connection_id}:mark_eol_time")
|> case do
nil -> nil ->
default DateTime.utc_now()
value -> value ->
value value
end end
end end
defp can_broadcast?(map_id), defp _can_broadcast?(map_id),
do: do:
not WandererApp.Cache.lookup!("map_#{map_id}:importing", false) and not WandererApp.Cache.lookup!("map_#{map_id}:importing", false) and
WandererApp.Cache.lookup!("map_#{map_id}:started", false) WandererApp.Cache.lookup!("map_#{map_id}:started", false)
defp update_location( defp _update_location(
character_id, character_id,
location, location,
old_location, old_location,
%{map: map, map_id: map_id, rtree_name: rtree_name, map_opts: map_opts} = _state %{map: map, map_id: map_id, rtree_name: rtree_name, map_opts: map_opts} = _state
) do ) do
case is_nil(old_location.solar_system_id) and case is_nil(old_location.solar_system_id) and
can_add_location(map.scope, location.solar_system_id) do _can_add_location(map.scope, location.solar_system_id) do
true -> true ->
:ok = maybe_add_system(map_id, location, nil, rtree_name, map_opts) :ok = maybe_add_system(map_id, location, nil, rtree_name, map_opts)
_ -> _ ->
case is_connection_valid( case _is_connection_valid(
map.scope, map.scope,
old_location.solar_system_id, old_location.solar_system_id,
location.solar_system_id location.solar_system_id
@@ -1050,7 +880,7 @@ defmodule WandererApp.Map.Server.Impl do
end end
end end
defp maybe_update_location(map_id, character_id) do defp _maybe_update_location(map_id, character_id) do
WandererApp.Cache.lookup!( WandererApp.Cache.lookup!(
"character:#{character_id}:location_started", "character:#{character_id}:location_started",
false false
@@ -1101,7 +931,7 @@ defmodule WandererApp.Map.Server.Impl do
end end
end end
defp maybe_update_alliance(map_id, character_id) do defp _maybe_update_alliance(map_id, character_id) do
with {:ok, old_alliance_id} <- with {:ok, old_alliance_id} <-
WandererApp.Cache.lookup("map:#{map_id}:character:#{character_id}:alliance_id"), WandererApp.Cache.lookup("map:#{map_id}:character:#{character_id}:alliance_id"),
{:ok, %{alliance_id: alliance_id}} <- {:ok, %{alliance_id: alliance_id}} <-
@@ -1125,7 +955,7 @@ defmodule WandererApp.Map.Server.Impl do
end end
end end
defp maybe_update_corporation(map_id, character_id) do defp _maybe_update_corporation(map_id, character_id) do
with {:ok, old_corporation_id} <- with {:ok, old_corporation_id} <-
WandererApp.Cache.lookup("map:#{map_id}:character:#{character_id}:corporation_id"), WandererApp.Cache.lookup("map:#{map_id}:character:#{character_id}:corporation_id"),
{:ok, %{corporation_id: corporation_id}} <- {:ok, %{corporation_id: corporation_id}} <-
@@ -1149,7 +979,7 @@ defmodule WandererApp.Map.Server.Impl do
end end
end end
defp maybe_update_online(map_id, character_id) do defp _maybe_update_online(map_id, character_id) do
with {:ok, old_online} <- with {:ok, old_online} <-
WandererApp.Cache.lookup("map:#{map_id}:character:#{character_id}:online"), WandererApp.Cache.lookup("map:#{map_id}:character:#{character_id}:online"),
{:ok, %{online: online}} <- {:ok, %{online: online}} <-
@@ -1173,7 +1003,7 @@ defmodule WandererApp.Map.Server.Impl do
end end
end end
defp maybe_update_ship(map_id, character_id) do defp _maybe_update_ship(map_id, character_id) do
with {:ok, old_ship_type_id} <- with {:ok, old_ship_type_id} <-
WandererApp.Cache.lookup("map:#{map_id}:character:#{character_id}:ship_type_id"), WandererApp.Cache.lookup("map:#{map_id}:character:#{character_id}:ship_type_id"),
{:ok, old_ship_name} <- {:ok, old_ship_name} <-
@@ -1205,7 +1035,7 @@ defmodule WandererApp.Map.Server.Impl do
end end
end end
defp update_connection( defp _update_connection(
%{map_id: map_id} = state, %{map_id: map_id} = state,
update_method, update_method,
attributes, attributes,
@@ -1221,7 +1051,7 @@ defmodule WandererApp.Map.Server.Impl do
solar_system_source_id, solar_system_source_id,
solar_system_target_id solar_system_target_id
), ),
{:ok, update_map} <- get_update_map(update, attributes), {:ok, update_map} <- _get_update_map(update, attributes),
:ok <- :ok <-
WandererApp.Map.update_connection( WandererApp.Map.update_connection(
map_id, map_id,
@@ -1247,7 +1077,7 @@ defmodule WandererApp.Map.Server.Impl do
end end
end end
defp update_system( defp _update_system(
%{map_id: map_id} = state, %{map_id: map_id} = state,
update_method, update_method,
attributes, attributes,
@@ -1260,7 +1090,7 @@ defmodule WandererApp.Map.Server.Impl do
map_id, map_id,
update.solar_system_id update.solar_system_id
), ),
{:ok, update_map} <- get_update_map(update, attributes) do {:ok, update_map} <- _get_update_map(update, attributes) do
{:ok, updated_system} = {:ok, updated_system} =
apply(WandererApp.MapSystemRepo, update_method, [ apply(WandererApp.MapSystemRepo, update_method, [
system, system,
@@ -1271,7 +1101,7 @@ defmodule WandererApp.Map.Server.Impl do
callback_fn.(updated_system) callback_fn.(updated_system)
end end
update_map_system_last_activity(map_id, updated_system) _update_map_system_last_activity(map_id, updated_system)
state state
else else
@@ -1281,7 +1111,7 @@ defmodule WandererApp.Map.Server.Impl do
end end
end end
defp get_update_map(update, attributes), defp _get_update_map(update, attributes),
do: do:
{:ok, {:ok,
Enum.reduce(attributes, Map.new(), fn attribute, map -> Enum.reduce(attributes, Map.new(), fn attribute, map ->
@@ -1325,8 +1155,7 @@ defmodule WandererApp.Map.Server.Impl do
rtree_name rtree_name
) )
existing_system {:ok, existing_system}
|> WandererApp.MapSystemRepo.update_visible(%{visible: true})
else else
@ddrt.insert( @ddrt.insert(
{solar_system_id, {solar_system_id,
@@ -1340,8 +1169,7 @@ defmodule WandererApp.Map.Server.Impl do
existing_system existing_system
|> WandererApp.MapSystemRepo.update_position!(%{position_x: x, position_y: y}) |> WandererApp.MapSystemRepo.update_position!(%{position_x: x, position_y: y})
|> WandererApp.MapSystemRepo.cleanup_labels!(map_opts) |> WandererApp.MapSystemRepo.cleanup_labels!(map_opts)
|> WandererApp.MapSystemRepo.cleanup_tags!() |> WandererApp.MapSystemRepo.cleanup_tags()
|> WandererApp.MapSystemRepo.update_visible(%{visible: true})
end end
_ -> _ ->
@@ -1387,7 +1215,7 @@ defmodule WandererApp.Map.Server.Impl do
state state
end end
defp save_map_state(%{map_id: map_id} = _state) do defp _save_map_state(%{map_id: map_id} = _state) do
systems_last_activity = systems_last_activity =
map_id map_id
|> WandererApp.Map.list_systems!() |> WandererApp.Map.list_systems!()
@@ -1421,7 +1249,7 @@ defmodule WandererApp.Map.Server.Impl do
}) })
end end
defp maybe_stop_rtree(%{rtree_name: rtree_name} = state) do defp _maybe_stop_rtree(%{rtree_name: rtree_name} = state) do
case Process.whereis(rtree_name) do case Process.whereis(rtree_name) do
nil -> nil ->
:ok :ok
@@ -1433,7 +1261,7 @@ defmodule WandererApp.Map.Server.Impl do
state state
end end
defp init_map_cache(%__MODULE__{map_id: map_id} = state) do defp _init_map_cache(%__MODULE__{map_id: map_id} = state) do
case WandererApp.Api.MapState.by_map_id(map_id) do case WandererApp.Api.MapState.by_map_id(map_id) do
{:ok, {:ok,
%{ %{
@@ -1465,9 +1293,9 @@ defmodule WandererApp.Map.Server.Impl do
end end
end end
defp init_map( defp _init_map(
state, state,
%{id: map_id, characters: characters} = initial_map, %{characters: characters} = initial_map,
subscription_settings, subscription_settings,
systems, systems,
connections connections
@@ -1483,24 +1311,16 @@ defmodule WandererApp.Map.Server.Impl do
{:ok, map_options} = WandererApp.MapRepo.options_to_form_data(initial_map) {:ok, map_options} = WandererApp.MapRepo.options_to_form_data(initial_map)
map_opts = [ map_opts = [
layout: map_options |> Map.get("layout", "left_to_right"), layout: map_options |> Map.get("layout"),
store_custom_labels: store_custom_labels: map_options |> Map.get("store_custom_labels")
map_options |> Map.get("store_custom_labels", "false") |> String.to_existing_atom()
] ]
character_ids =
map_id
|> WandererApp.Map.get_map!()
|> Map.get(:characters, [])
WandererApp.Cache.insert("map_#{map_id}:invalidate_character_ids", character_ids)
%{state | map: map, map_opts: map_opts} %{state | map: map, map_opts: map_opts}
end end
defp init_map_systems(state, [] = _systems), do: state defp _init_map_systems(state, [] = _systems), do: state
defp init_map_systems(%__MODULE__{map_id: map_id, rtree_name: rtree_name} = state, systems) do defp _init_map_systems(%__MODULE__{map_id: map_id, rtree_name: rtree_name} = state, systems) do
systems systems
|> Enum.each(fn %{id: system_id, solar_system_id: solar_system_id} = system -> |> Enum.each(fn %{id: system_id, solar_system_id: solar_system_id} = system ->
@ddrt.insert( @ddrt.insert(
@@ -1518,7 +1338,7 @@ defmodule WandererApp.Map.Server.Impl do
state state
end end
def maybe_import_systems(state, %{"systems" => systems} = _settings, user_id, character_id) do def _maybe_import_systems(state, %{"systems" => systems} = _settings, user_id, character_id) do
state = state =
systems systems
|> Enum.reduce(state, fn %{ |> Enum.reduce(state, fn %{
@@ -1562,7 +1382,7 @@ defmodule WandererApp.Map.Server.Impl do
|> delete_systems(removed_system_ids, user_id, character_id) |> delete_systems(removed_system_ids, user_id, character_id)
end end
def maybe_import_connections(state, %{"connections" => connections} = _settings, _user_id) do def _maybe_import_connections(state, %{"connections" => connections} = _settings, _user_id) do
connections connections
|> Enum.reduce(state, fn %{ |> Enum.reduce(state, fn %{
"source" => source, "source" => source,
@@ -1598,7 +1418,7 @@ defmodule WandererApp.Map.Server.Impl do
end) end)
end end
def maybe_import_hubs(state, %{"hubs" => hubs} = _settings, _user_id) do def _maybe_import_hubs(state, %{"hubs" => hubs} = _settings, _user_id) do
hubs hubs
|> Enum.reduce(state, fn hub, acc -> |> Enum.reduce(state, fn hub, acc ->
solar_system_id = hub |> String.to_integer() solar_system_id = hub |> String.to_integer()
@@ -1608,7 +1428,7 @@ defmodule WandererApp.Map.Server.Impl do
end) end)
end end
defp update_map_system_last_activity( defp _update_map_system_last_activity(
map_id, map_id,
updated_system updated_system
) do ) do
@@ -1621,13 +1441,13 @@ defmodule WandererApp.Map.Server.Impl do
broadcast!(map_id, :update_system, updated_system) broadcast!(map_id, :update_system, updated_system)
end end
defp can_add_location(_scope, nil), do: false defp _can_add_location(_scope, nil), do: false
defp can_add_location(:all, _solar_system_id), do: true defp _can_add_location(:all, _solar_system_id), do: true
defp can_add_location(:none, _solar_system_id), do: false defp _can_add_location(:none, _solar_system_id), do: false
defp can_add_location(scope, solar_system_id) do defp _can_add_location(scope, solar_system_id) do
system_static_info = system_static_info =
case WandererApp.CachedInfo.get_system_static_info(solar_system_id) do case WandererApp.CachedInfo.get_system_static_info(solar_system_id) do
{:ok, system_static_info} when not is_nil(system_static_info) -> {:ok, system_static_info} when not is_nil(system_static_info) ->
@@ -1652,14 +1472,14 @@ defmodule WandererApp.Map.Server.Impl do
end end
end end
defp is_connection_exist(map_id, from_solar_system_id, to_solar_system_id), defp _is_connection_exist(map_id, from_solar_system_id, to_solar_system_id),
do: do:
not is_nil( not is_nil(
WandererApp.Map.find_system_by_location( WandererApp.Map.find_system_by_location(
map_id, map_id,
%{solar_system_id: from_solar_system_id} %{solar_system_id: from_solar_system_id}
) )
) && ) and
not is_nil( not is_nil(
WandererApp.Map.find_system_by_location( WandererApp.Map.find_system_by_location(
map_id, map_id,
@@ -1667,13 +1487,13 @@ defmodule WandererApp.Map.Server.Impl do
) )
) )
defp is_connection_valid(_scope, nil, _to_solar_system_id), do: false defp _is_connection_valid(_scope, nil, _to_solar_system_id), do: false
defp is_connection_valid(:all, _from_solar_system_id, _to_solar_system_id), do: true defp _is_connection_valid(:all, _from_solar_system_id, _to_solar_system_id), do: true
defp is_connection_valid(:none, _from_solar_system_id, _to_solar_system_id), do: false defp _is_connection_valid(:none, _from_solar_system_id, _to_solar_system_id), do: false
defp is_connection_valid(scope, from_solar_system_id, to_solar_system_id) do defp _is_connection_valid(scope, from_solar_system_id, to_solar_system_id) do
{:ok, known_jumps} = {:ok, known_jumps} =
WandererApp.Api.MapSolarSystemJumps.find(%{ WandererApp.Api.MapSolarSystemJumps.find(%{
before_system_id: from_solar_system_id, before_system_id: from_solar_system_id,
@@ -1702,7 +1522,7 @@ defmodule WandererApp.Map.Server.Impl do
end end
end end
defp update_presence(map_id) do defp _update_presence(map_id) do
case WandererApp.Cache.lookup!("map_#{map_id}:started", false) and case WandererApp.Cache.lookup!("map_#{map_id}:started", false) and
WandererApp.Cache.get_and_remove!("map_#{map_id}:presence_updated", false) do WandererApp.Cache.get_and_remove!("map_#{map_id}:presence_updated", false) do
true -> true ->
@@ -1720,7 +1540,7 @@ defmodule WandererApp.Map.Server.Impl do
not Enum.member?(presence_character_ids, character_id) not Enum.member?(presence_character_ids, character_id)
end) end)
track_characters(presence_character_ids, map_id) _track_characters(presence_character_ids, map_id)
map_id map_id
|> _untrack_characters(not_present_character_ids) |> _untrack_characters(not_present_character_ids)
@@ -1739,32 +1559,34 @@ defmodule WandererApp.Map.Server.Impl do
end end
end end
defp track_acls([]), do: :ok defp _track_acls([]), do: :ok
defp track_acls([acl_id | rest]) do defp _track_acls([acl_id | rest]) do
track_acl(acl_id) _track_acl(acl_id)
track_acls(rest) _track_acls(rest)
end end
defp track_acl(acl_id), defp _track_acl(acl_id),
do: @pubsub_client.subscribe(WandererApp.PubSub, "acls:#{acl_id}")
defp track_characters([], _map_id), do: :ok
defp track_characters([character_id | rest], map_id) do
track_character(character_id, map_id)
track_characters(rest, map_id)
end
defp track_character(character_id, map_id),
do: do:
WandererApp.Character.TrackerManager.update_track_settings(character_id, %{ WandererApp.PubSub
map_id: map_id, |> @pubsub_client.subscribe("acls:#{acl_id}")
track: true,
track_online: true, defp _track_characters([], _map_id), do: :ok
track_location: true,
track_ship: true defp _track_characters([character_id | rest], map_id) do
}) _track_character(character_id, map_id)
_track_characters(rest, map_id)
end
defp _track_character(character_id, map_id) do
WandererApp.Character.TrackerManager.update_track_settings(character_id, %{
map_id: map_id,
track: true,
track_online: true,
track_location: true,
track_ship: true
})
end
defp _update_character(map_id, character_id) do defp _update_character(map_id, character_id) do
{:ok, character} = WandererApp.Character.get_character(character_id) {:ok, character} = WandererApp.Character.get_character(character_id)
@@ -1926,14 +1748,12 @@ defmodule WandererApp.Map.Server.Impl do
broadcast!(map_id, :add_system, new_system) broadcast!(map_id, :add_system, new_system)
WandererApp.Map.add_system(map_id, new_system) WandererApp.Map.add_system(map_id, new_system)
error -> _ ->
@logger.debug("Failed to create system: #{inspect(error, pretty: true)}")
:ok :ok
end end
end end
error -> {:error, _} ->
@logger.debug("Skip adding system: #{inspect(error, pretty: true)}")
:ok :ok
end end
end end
@@ -1947,14 +1767,13 @@ defmodule WandererApp.Map.Server.Impl do
|> WandererApp.Map.find_system_by_location(old_location) |> WandererApp.Map.find_system_by_location(old_location)
|> WandererApp.Map.PositionCalculator.get_new_system_position(rtree_name, opts)} |> WandererApp.Map.PositionCalculator.get_new_system_position(rtree_name, opts)}
defp broadcast_acl_updates( defp _broadcast_acl_updates(
{:ok, {:ok,
%{ %{
eve_character_ids: eve_character_ids, eve_character_ids: eve_character_ids,
eve_corporation_ids: eve_corporation_ids, eve_corporation_ids: eve_corporation_ids,
eve_alliance_ids: eve_alliance_ids eve_alliance_ids: eve_alliance_ids
}}, }}
map_id
) do ) do
eve_character_ids eve_character_ids
|> Enum.uniq() |> Enum.uniq()
@@ -1986,19 +1805,12 @@ defmodule WandererApp.Map.Server.Impl do
) )
end) end)
character_ids =
map_id
|> WandererApp.Map.get_map!()
|> Map.get(:characters, [])
WandererApp.Cache.insert("map_#{map_id}:invalidate_character_ids", character_ids)
:ok :ok
end end
defp broadcast_acl_updates(_, _map_id), do: :ok defp _broadcast_acl_updates(_), do: :ok
defp update_acl(acl_id) do defp _update_acl(acl_id) do
{:ok, %{owner: owner, members: members}} = {:ok, %{owner: owner, members: members}} =
WandererApp.AccessListRepo.get(acl_id, [:owner, :members]) WandererApp.AccessListRepo.get(acl_id, [:owner, :members])

View File

@@ -87,113 +87,4 @@ defmodule WandererApp.Permissions do
delete_map: check_permission(user_permissions, @delete_map) delete_map: check_permission(user_permissions, @delete_map)
} }
end end
def check_characters_access(characters, acls) do
character_ids = characters |> Enum.map(& &1.id)
character_eve_ids = characters |> Enum.map(& &1.eve_id)
character_corporation_ids =
characters |> Enum.map(& &1.corporation_id) |> Enum.map(&to_string/1)
character_alliance_ids = characters |> Enum.map(& &1.alliance_id) |> Enum.map(&to_string/1)
result =
acls
|> Enum.reduce([0, 0], fn acl, acc ->
is_owner? = acl.owner_id in character_ids
is_character_member? =
acl.members |> Enum.any?(fn member -> member.eve_character_id in character_eve_ids end)
is_corporation_member? =
acl.members
|> Enum.any?(fn member -> member.eve_corporation_id in character_corporation_ids end)
is_alliance_member? =
acl.members
|> Enum.any?(fn member -> member.eve_alliance_id in character_alliance_ids end)
if is_owner? || is_character_member? || is_corporation_member? || is_alliance_member? do
case acc do
[_, -1] ->
[-1, -1]
[-1, char_acc] ->
char_acl_mask =
acl.members
|> Enum.filter(fn member ->
member.eve_character_id in character_eve_ids
end)
|> Enum.reduce(0, fn member, acc ->
case acc do
-1 -> -1
_ -> calc_role_mask(member.role, acc)
end
end)
char_acc =
case char_acl_mask do
-1 -> -1
_ -> char_acc ||| char_acl_mask
end
[-1, char_acc]
[any_acc, char_acc] ->
any_acl_mask =
acl.members
|> Enum.filter(fn member ->
member.eve_character_id in character_eve_ids ||
member.eve_corporation_id in character_corporation_ids ||
member.eve_alliance_id in character_alliance_ids
end)
|> Enum.reduce(0, fn member, acc ->
case acc do
-1 -> -1
_ -> calc_role_mask(member.role, acc)
end
end)
char_acl_mask =
acl.members
|> Enum.filter(fn member ->
member.eve_character_id in character_eve_ids
end)
|> Enum.reduce(0, fn member, acc ->
case acc do
-1 -> -1
_ -> calc_role_mask(member.role, acc)
end
end)
any_acc =
case any_acl_mask do
-1 -> -1
_ -> any_acc ||| any_acl_mask
end
char_acc =
case char_acl_mask do
-1 -> -1
_ -> char_acc ||| char_acl_mask
end
[any_acc, char_acc]
end
else
acc
end
end)
case result do
[_, -1] ->
[-1]
[-1, char_acc] ->
[char_acc]
[any_acc, _char_acc] ->
[any_acc]
end
end
end end

View File

@@ -3,23 +3,4 @@ defmodule WandererApp.MapCharacterSettingsRepo do
def create(settings), def create(settings),
do: WandererApp.Api.MapCharacterSettings.create(settings) do: WandererApp.Api.MapCharacterSettings.create(settings)
def get_tracked_by_map_filtered(map_id, character_ids),
do:
WandererApp.Api.MapCharacterSettings.tracked_by_map_filtered(%{
map_id: map_id,
character_ids: character_ids
})
def get_all_by_map(map_id),
do: WandererApp.Api.MapCharacterSettings.read_by_map(%{map_id: map_id})
def get_tracked_by_map_all(map_id),
do: WandererApp.Api.MapCharacterSettings.tracked_by_map_all(%{map_id: map_id})
def track(settings), do: settings |> WandererApp.Api.MapCharacterSettings.track()
def untrack(settings), do: settings |> WandererApp.Api.MapCharacterSettings.untrack()
def track!(settings), do: settings |> WandererApp.Api.MapCharacterSettings.track!()
def untrack!(settings), do: settings |> WandererApp.Api.MapCharacterSettings.untrack!()
end end

View File

@@ -35,12 +35,12 @@ defmodule WandererApp.MapSystemRepo do
def cleanup_labels!(%{labels: labels} = system, opts) do def cleanup_labels!(%{labels: labels} = system, opts) do
store_custom_labels? = store_custom_labels? =
Keyword.get(opts, :store_custom_labels) Keyword.get(opts, :store_custom_labels, "false") |> String.to_existing_atom()
labels = get_filtered_labels(labels, store_custom_labels?) labels = get_filtered_labels(labels, store_custom_labels?)
system system
|> update_labels!(%{ |> WandererApp.Api.MapSystem.update_labels!(%{
labels: labels labels: labels
}) })
end end
@@ -52,13 +52,6 @@ defmodule WandererApp.MapSystemRepo do
}) })
end end
def cleanup_tags!(system) do
system
|> WandererApp.Api.MapSystem.update_tag!(%{
tag: nil
})
end
def get_filtered_labels(labels, true) when is_binary(labels) do def get_filtered_labels(labels, true) when is_binary(labels) do
labels labels
|> Jason.decode!() |> Jason.decode!()
@@ -104,11 +97,6 @@ defmodule WandererApp.MapSystemRepo do
system system
|> WandererApp.Api.MapSystem.update_labels(update) |> WandererApp.Api.MapSystem.update_labels(update)
def update_labels!(system, update),
do:
system
|> WandererApp.Api.MapSystem.update_labels!(update)
def update_position(system, update), def update_position(system, update),
do: do:
system system
@@ -118,14 +106,4 @@ defmodule WandererApp.MapSystemRepo do
do: do:
system system
|> WandererApp.Api.MapSystem.update_position!(update) |> WandererApp.Api.MapSystem.update_position!(update)
def update_visible(system, update),
do:
system
|> WandererApp.Api.MapSystem.update_visible(update)
def update_visible!(system, update),
do:
system
|> WandererApp.Api.MapSystem.update_visible!(update)
end end

View File

@@ -1,5 +1,4 @@
defmodule WandererAppWeb.AccessListsLive do defmodule WandererAppWeb.AccessListsLive do
alias Pathex.Builder.Viewer
use WandererAppWeb, :live_view use WandererAppWeb, :live_view
require Logger require Logger
@@ -90,10 +89,7 @@ defmodule WandererAppWeb.AccessListsLive do
|> assign(:page_title, "Access Lists - Members") |> assign(:page_title, "Access Lists - Members")
|> assign(:selected_acl_id, acl_id) |> assign(:selected_acl_id, acl_id)
|> assign(:access_list, access_list) |> assign(:access_list, access_list)
|> assign( |> assign(:members, members)
:members,
members
)
else else
_ -> _ ->
socket socket
@@ -149,7 +145,7 @@ defmodule WandererAppWeb.AccessListsLive do
:send_after, :send_after,
[self(), {:search, text}, 100], [self(), {:search, text}, 100],
"member_search_#{socket.assigns.selected_acl_id}", "member_search_#{socket.assigns.selected_acl_id}",
250 500
) )
[%{label: "Loading...", value: :loading, disabled: true}] [%{label: "Loading...", value: :loading, disabled: true}]
@@ -292,11 +288,7 @@ defmodule WandererAppWeb.AccessListsLive do
end end
@impl true @impl true
def handle_event( def handle_event("dropped", %{"draggedId" => dragged_id, "dropzoneId" => dropzone_id}, socket) do
"dropped",
%{"draggedId" => dragged_id, "dropzoneId" => dropzone_id},
%{assigns: %{access_list: access_list, members: members}} = socket
) do
role_atom = role_atom =
[:admin, :manager, :member, :viewer, :blocked] [:admin, :manager, :member, :viewer, :blocked]
|> Enum.find(fn role_atom -> to_string(role_atom) == dropzone_id end) |> Enum.find(fn role_atom -> to_string(role_atom) == dropzone_id end)
@@ -307,27 +299,13 @@ defmodule WandererAppWeb.AccessListsLive do
role_atom -> role_atom ->
member = member =
members socket.assigns.members
|> Enum.find(&(&1.id == dragged_id)) |> Enum.find(&(&1.id == dragged_id))
{:noreply, socket |> maybe_update_role(member, role_atom, access_list)} {:noreply, socket |> maybe_update_role(member, role_atom, socket.assigns.access_list)}
end end
end end
@impl true
def handle_info(
{"update_role", %{member_id: member_id, role: role}},
%{assigns: %{access_list: access_list, members: members}} = socket
) do
role_atom = role |> String.to_existing_atom()
member =
members
|> Enum.find(&(&1.id == member_id))
{:noreply, socket |> maybe_update_role(member, role_atom, access_list)}
end
@impl true @impl true
def handle_event("noop", _, socket) do def handle_event("noop", _, socket) do
{:noreply, socket} {:noreply, socket}
@@ -347,33 +325,10 @@ defmodule WandererAppWeb.AccessListsLive do
|> Enum.map(& &1.id) |> Enum.map(& &1.id)
|> Enum.at(0) |> Enum.at(0)
uniq_search_req_id = UUID.uuid4(:default) {:ok, options} = search(active_character_id, text)
Task.async(fn -> send_update(LiveSelect.Component, options: options, id: socket.assigns.member_search_id)
{:ok, options} = search(active_character_id, text) {:noreply, socket |> assign(member_search_options: options)}
{:search_results, uniq_search_req_id, options}
end)
{:noreply, socket |> assign(uniq_search_req_id: uniq_search_req_id)}
end
def handle_info(
{ref, result},
%{assigns: %{member_search_id: member_search_id, uniq_search_req_id: uniq_search_req_id}} =
socket
)
when is_reference(ref) do
Process.demonitor(ref, [:flush])
case result do
{:search_results, ^uniq_search_req_id, options} ->
send_update(LiveSelect.Component, options: options, id: member_search_id)
{:noreply, socket |> assign(member_search_options: options)}
_ ->
{:noreply, socket}
end
end end
@impl true @impl true
@@ -448,7 +403,6 @@ defmodule WandererAppWeb.AccessListsLive do
_ -> _ ->
socket socket
|> put_flash(:error, "You're not allowed to assign this role") |> put_flash(:error, "You're not allowed to assign this role")
|> push_navigate(to: ~p"/access-lists/#{socket.assigns.selected_acl_id}")
end end
end end
@@ -457,11 +411,10 @@ defmodule WandererAppWeb.AccessListsLive do
_member, _member,
_role_atom, _role_atom,
_access_list _access_list
), ) do
do: socket
socket |> put_flash(:info, "Only Characters can have Admin or Manager roles")
|> put_flash(:info, "Only Characters can have Admin or Manager roles") end
|> push_navigate(to: ~p"/access-lists/#{socket.assigns.selected_acl_id}")
defp characters_has_role?(character_eve_ids, access_list, role_atom) do defp characters_has_role?(character_eve_ids, access_list, role_atom) do
access_list.members access_list.members
@@ -661,6 +614,27 @@ defmodule WandererAppWeb.AccessListsLive do
""" """
end end
def member_item(assigns) do
~H"""
<div class="flex items-center gap-2">
<.icon :if={not is_nil(@member.role)} name={member_role_icon(@member.role)} class="w-6 h-6" />
<div class="avatar">
<div class="rounded-md w-8 h-8">
<img src={member_icon_url(@member)} alt={@member.name} />
</div>
</div>
<%= @member.name %>
</div>
"""
end
def member_role_icon(:admin), do: "hero-user-group-solid"
def member_role_icon(:manager), do: "hero-academic-cap-solid"
def member_role_icon(:member), do: "hero-user-solid"
def member_role_icon(:viewer), do: "hero-eye-solid"
def member_role_icon(:blocked), do: "hero-no-symbol-solid text-red-500"
def member_role_icon(_), do: "hero-cake-solid"
def search_member_icon_url(%{character: true} = option), def search_member_icon_url(%{character: true} = option),
do: member_icon_url(%{eve_character_id: option.value}) do: member_icon_url(%{eve_character_id: option.value})

View File

@@ -82,20 +82,14 @@
id="acl_members" id="acl_members"
> >
<div <div
:for={member <- @members |> Enum.sort_by(&{&1.role, &1.name}, &<=/2)} :for={member <- @members |> Enum.sort(&(&1.name < &2.name))}
draggable="true" draggable="true"
id={member.id} id={member.id}
class="draggable !p-1 h-10 cursor-move bg-black bg-opacity-25 hover:text-white" class="draggable !p-1 h-10 cursor-move bg-black bg-opacity-25 hover:text-white"
data-dropzone="pool" data-dropzone="pool"
> >
<div class="flex justify-between relative"> <div class="flex justify-between relative">
<.live_component <.member_item member={member} />
module={WandererAppWeb.AclMember}
id={"select_role_" <> member.id}
notify_to={self()}
member={member}
event_name="update_role"
/>
<button <button
:if={can_delete_member?(member, @access_list, @current_user)} :if={can_delete_member?(member, @access_list, @current_user)}
class="z-10 absolute top-0 right-2" class="z-10 absolute top-0 right-2"
@@ -156,71 +150,6 @@
show show
on_cancel={JS.patch(~p"/access-lists/#{@selected_acl_id}")} on_cancel={JS.patch(~p"/access-lists/#{@selected_acl_id}")}
> >
<%!-- <div class="mt-4 mb-2 p-tabmenu p-component " data-pc-section="tabmenu">
<ul
class="p-tabmenu-nav border-none h-[25px] w-full flex"
role="menubar"
data-pc-section="menu"
>
<li
id="pr_id_17_0"
class="p-tabmenuitem p-highlight"
role="presentation"
data-p-highlight="true"
data-p-disabled="false"
data-pc-section="menuitem"
>
<a
href="#"
role="menuitem"
aria-label="Router Link"
tabindex="0"
class="p-menuitem-link"
data-pc-section="action"
>
<span class="p-menuitem-text" data-pc-section="label">Character</span>
</a>
</li>
<li
id="pr_id_17_1"
class="p-tabmenuitem"
role="presentation"
data-p-highlight="false"
data-p-disabled="false"
data-pc-section="menuitem"
>
<a
href="#"
role="menuitem"
aria-label="Programmatic"
tabindex="-1"
class="p-menuitem-link"
data-pc-section="action"
>
<span class="p-menuitem-text" data-pc-section="label">Corporation</span>
</a>
</li>
<li
id="pr_id_17_2"
class="p-tabmenuitem"
role="presentation"
data-p-highlight="false"
data-p-disabled="false"
data-pc-section="menuitem"
>
<a
href="#"
role="menuitem"
aria-label="External"
tabindex="-1"
class="p-menuitem-link"
data-pc-section="action"
>
<span class="p-menuitem-text" data-pc-section="label">Alliance</span>
</a>
</li>
</ul>
</div> --%>
<.form :let={f} for={@member_form} phx-submit={@live_action}> <.form :let={f} for={@member_form} phx-submit={@live_action}>
<.live_select <.live_select
field={f[:member_id]} field={f[:member_id]}

View File

@@ -1,86 +0,0 @@
defmodule WandererAppWeb.AclMember do
use WandererAppWeb, :live_component
use LiveViewEvents
@roles [
:admin,
:manager,
:member,
:viewer,
:blocked
]
@impl true
def mount(socket) do
{:ok, socket |> assign(roles: get_roles())}
end
@impl true
def update(
%{
member: member
} = assigns,
socket
) do
socket = handle_info_or_assign(socket, assigns)
{:ok,
socket
|> assign(member: member, form: to_form(%{"role" => member.role}))}
end
@impl true
def render(assigns) do
~H"""
<div id={@id} class="flex items-center gap-2">
<.icon :if={not is_nil(@member.role)} name={member_role_icon(@member.role)} class="w-6 h-6" />
<.form :let={f} id={"role_form_" <> @id} for={@form} phx-change="select" phx-target={@myself}>
<.input
type="select"
field={f[:role]}
class="select h-8 min-h-[0px] !pt-1 !pb-1 text-sm bg-neutral-900 w-[70px]"
placeholder="Select a role..."
options={Enum.map(@roles, fn role -> {role.label, role.value} end)}
/>
</.form>
<div class="avatar">
<div class="rounded-md w-8 h-8">
<img src={member_icon_url(@member)} alt={@member.name} />
</div>
</div>
<%= @member.name %>
</div>
"""
end
@impl true
def handle_event(
"select",
%{"role" => role} = _params,
%{assigns: %{event_name: event_name, member: member, notify_to: notify_to}} = socket
) do
notify_to(notify_to, event_name, %{
member_id: member.id,
role: role
})
{:noreply, socket}
end
def member_role_icon(:admin), do: "hero-user-group-solid"
def member_role_icon(:manager), do: "hero-academic-cap-solid"
def member_role_icon(:member), do: "hero-user-solid"
def member_role_icon(:viewer), do: "hero-eye-solid"
def member_role_icon(:blocked), do: "hero-no-symbol-solid text-red-500"
def member_role_icon(_), do: "hero-cake-solid"
def member_role_title(:admin), do: "Admin"
def member_role_title(:manager), do: "Manager"
def member_role_title(:member), do: "Member"
def member_role_title(:viewer), do: "Viewer"
def member_role_title(:blocked), do: "-blocked-"
def member_role_title(_), do: "-"
defp get_roles(), do: @roles |> Enum.map(&%{label: member_role_title(&1), value: &1})
end

View File

@@ -41,7 +41,7 @@ defmodule WandererAppWeb.CharactersTrackingLive do
selected_map = socket.assigns.maps |> Enum.find(&(&1.slug == map_slug)) selected_map = socket.assigns.maps |> Enum.find(&(&1.slug == map_slug))
{:ok, character_settings} = {:ok, character_settings} =
case WandererApp.MapCharacterSettingsRepo.get_all_by_map(selected_map.id) do case WandererApp.Api.MapCharacterSettings.read_by_map(%{map_id: selected_map.id}) do
{:ok, settings} -> {:ok, settings} ->
{:ok, settings} {:ok, settings}
@@ -83,7 +83,7 @@ defmodule WandererAppWeb.CharactersTrackingLive do
case character_settings |> Enum.find(&(&1.character_id == character_id)) do case character_settings |> Enum.find(&(&1.character_id == character_id)) do
nil -> nil ->
WandererApp.MapCharacterSettingsRepo.create(%{ WandererApp.Api.MapCharacterSettings.create(%{
character_id: character_id, character_id: character_id,
map_id: selected_map.id, map_id: selected_map.id,
tracked: true tracked: true
@@ -95,18 +95,18 @@ defmodule WandererAppWeb.CharactersTrackingLive do
case character_setting.tracked do case character_setting.tracked do
true -> true ->
character_setting character_setting
|> WandererApp.MapCharacterSettingsRepo.untrack!() |> WandererApp.Api.MapCharacterSettings.untrack!()
_ -> _ ->
character_setting character_setting
|> WandererApp.MapCharacterSettingsRepo.track!() |> WandererApp.Api.MapCharacterSettings.track!()
end end
end end
%{result: characters} = socket.assigns.characters %{result: characters} = socket.assigns.characters
{:ok, character_settings} = {:ok, character_settings} =
case WandererApp.MapCharacterSettingsRepo.get_all_by_map(selected_map.id) do case WandererApp.Api.MapCharacterSettings.read_by_map(%{map_id: selected_map.id}) do
{:ok, settings} -> {:ok, settings} {:ok, settings} -> {:ok, settings}
_ -> {:ok, []} _ -> {:ok, []}
end end

View File

@@ -75,7 +75,7 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
} = socket } = socket
) do ) do
{:ok, character_settings} = {:ok, character_settings} =
case WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id) do case WandererApp.Api.MapCharacterSettings.read_by_map(%{map_id: map_id}) do
{:ok, settings} -> {:ok, settings} {:ok, settings} -> {:ok, settings}
_ -> {:ok, []} _ -> {:ok, []}
end end
@@ -132,7 +132,7 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
case character_settings |> Enum.find(&(&1.character_id == character_id)) do case character_settings |> Enum.find(&(&1.character_id == character_id)) do
nil -> nil ->
{:ok, map_character_settings} = {:ok, map_character_settings} =
WandererApp.MapCharacterSettingsRepo.create(%{ WandererApp.Api.MapCharacterSettings.create(%{
character_id: character_id, character_id: character_id,
map_id: map_id, map_id: map_id,
tracked: true tracked: true
@@ -150,7 +150,7 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
true -> true ->
{:ok, map_character_settings} = {:ok, map_character_settings} =
character_setting character_setting
|> WandererApp.MapCharacterSettingsRepo.untrack() |> WandererApp.Api.MapCharacterSettings.untrack()
character = map_character_settings |> Ash.load!(:character) |> Map.get(:character) character = map_character_settings |> Ash.load!(:character) |> Map.get(:character)
@@ -166,7 +166,7 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
_ -> _ ->
{:ok, map_character_settings} = {:ok, map_character_settings} =
character_setting character_setting
|> WandererApp.MapCharacterSettingsRepo.track() |> WandererApp.Api.MapCharacterSettings.track()
character = map_character_settings |> Ash.load!(:character) |> Map.get(:character) character = map_character_settings |> Ash.load!(:character) |> Map.get(:character)
@@ -184,7 +184,7 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
user_character_eve_ids = map_characters |> Enum.map(& &1.eve_id) user_character_eve_ids = map_characters |> Enum.map(& &1.eve_id)
{:ok, character_settings} = {:ok, character_settings} =
case WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id) do case WandererApp.Api.MapCharacterSettings.read_by_map(%{map_id: map_id}) do
{:ok, settings} -> {:ok, settings} {:ok, settings} -> {:ok, settings}
_ -> {:ok, []} _ -> {:ok, []}
end end
@@ -225,10 +225,10 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
def has_tracked_characters?(_user_characters), do: true def has_tracked_characters?(_user_characters), do: true
def get_tracked_map_characters(map_id, current_user) do def get_tracked_map_characters(map_id, current_user) do
case WandererApp.MapCharacterSettingsRepo.get_tracked_by_map_filtered( case WandererApp.Api.MapCharacterSettings.tracked_by_map(%{
map_id, map_id: map_id,
current_user.characters |> Enum.map(& &1.id) character_ids: current_user.characters |> Enum.map(& &1.id)
) do }) do
{:ok, settings} -> {:ok, settings} ->
{:ok, {:ok,
settings settings

View File

@@ -162,16 +162,6 @@ defmodule WandererAppWeb.MapConnectionsEventHandler do
{:noreply, socket} {:noreply, socket}
end end
def handle_ui_event(
"get_connection_info",
%{"from" => from, "to" => to} = _event,
%{assigns: %{map_id: map_id}} = socket
) do
{:ok, info} = map_id |> get_connection_info(from, to)
{:reply, info, socket}
end
def handle_ui_event( def handle_ui_event(
"get_passages", "get_passages",
%{"from" => from, "to" => to} = _event, %{"from" => from, "to" => to} = _event,
@@ -204,19 +194,4 @@ defmodule WandererAppWeb.MapConnectionsEventHandler do
{:ok, %{passages: passages}} {:ok, %{passages: passages}}
end end
defp get_connection_info(map_id, from, to) do
map_id
|> WandererApp.Map.Server.get_connection_info(%{
solar_system_source_id: "#{from}" |> String.to_integer(),
solar_system_target_id: "#{to}" |> String.to_integer()
})
|> case do
{:ok, info} ->
{:ok, info}
_ ->
{:ok, %{}}
end
end
end end

View File

@@ -41,10 +41,10 @@ defmodule WandererAppWeb.MapCoreEventHandler do
%{track_character: track_character} -> %{track_character: track_character} ->
{:ok, map_characters} = {:ok, map_characters} =
case WandererApp.MapCharacterSettingsRepo.get_tracked_by_map_filtered( case WandererApp.Api.MapCharacterSettings.tracked_by_map(%{
map_id, map_id: map_id,
current_user.characters |> Enum.map(& &1.id) character_ids: current_user.characters |> Enum.map(& &1.id)
) do }) do
{:ok, settings} -> {:ok, settings} ->
{:ok, {:ok,
settings settings
@@ -258,7 +258,7 @@ defmodule WandererAppWeb.MapCoreEventHandler do
) )
{:ok, character_settings} = {:ok, character_settings} =
case WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id) do case WandererApp.Api.MapCharacterSettings.read_by_map(%{map_id: map_id}) do
{:ok, settings} -> {:ok, settings} {:ok, settings} -> {:ok, settings}
_ -> {:ok, []} _ -> {:ok, []}
end end
@@ -511,10 +511,10 @@ defmodule WandererAppWeb.MapCoreEventHandler do
end end
defp get_tracked_map_characters(map_id, current_user) do defp get_tracked_map_characters(map_id, current_user) do
case WandererApp.MapCharacterSettingsRepo.get_tracked_by_map_filtered( case WandererApp.Api.MapCharacterSettings.tracked_by_map(%{
map_id, map_id: map_id,
current_user.characters |> Enum.map(& &1.id) character_ids: current_user.characters |> Enum.map(& &1.id)
) do }) do
{:ok, settings} -> {:ok, settings} ->
{:ok, {:ok,
settings settings

View File

@@ -225,7 +225,6 @@ defmodule WandererAppWeb.MapSignaturesEventHandler do
|> Enum.filter(fn s -> s.eve_id == signature_eve_id end) |> Enum.filter(fn s -> s.eve_id == signature_eve_id end)
|> Enum.each(fn s -> |> Enum.each(fn s ->
s s
|> WandererApp.Api.MapSystemSignature.update_group!(%{group: "Wormhole"})
|> WandererApp.Api.MapSystemSignature.update_linked_system(%{ |> WandererApp.Api.MapSystemSignature.update_linked_system(%{
linked_system_id: solar_system_target linked_system_id: solar_system_target
}) })

View File

@@ -61,7 +61,6 @@ defmodule WandererAppWeb.MapEventHandler do
@map_connection_ui_events [ @map_connection_ui_events [
"manual_add_connection", "manual_add_connection",
"manual_delete_connection", "manual_delete_connection",
"get_connection_info",
"get_passages", "get_passages",
"update_connection_time_status", "update_connection_time_status",
"update_connection_mass_status", "update_connection_mass_status",
@@ -154,7 +153,7 @@ defmodule WandererAppWeb.MapEventHandler do
def handle_ui_event(event, body, socket) def handle_ui_event(event, body, socket)
when event in @map_characters_ui_events, when event in @map_characters_ui_events,
do: MapCharactersEventHandler.handle_ui_event(event, body, socket) do: MapSystemsEventHandler.handle_ui_event(event, body, socket)
def handle_ui_event(event, body, socket) def handle_ui_event(event, body, socket)
when event in @map_system_ui_events, when event in @map_system_ui_events,

View File

@@ -848,9 +848,9 @@ defmodule WandererAppWeb.MapsLive do
|> Enum.map(fn acl -> acl |> Ash.load!(:members) end) |> Enum.map(fn acl -> acl |> Ash.load!(:members) end)
{:ok, characters_count} = {:ok, characters_count} =
map.id case WandererApp.Api.MapCharacterSettings.tracked_by_map_all(%{
|> WandererApp.MapCharacterSettingsRepo.get_tracked_by_map_all() map_id: map.id
|> case do }) do
{:ok, settings} -> {:ok, settings} ->
{:ok, {:ok,
settings settings

View File

@@ -2,7 +2,7 @@ defmodule WandererApp.MixProject do
use Mix.Project use Mix.Project
@source_url "https://github.com/wanderer-industries/wanderer" @source_url "https://github.com/wanderer-industries/wanderer"
@version "1.18.0" @version "1.13.7"
def project do def project do
[ [

View File

@@ -0,0 +1,54 @@
defmodule WandererApp.Repo.Migrations.InstallAshFunctionsExtension420240922090427 do
@moduledoc """
Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback
This file was autogenerated with `mix ash_postgres.generate_migrations`
"""
use Ecto.Migration
def up do
execute("""
CREATE OR REPLACE FUNCTION uuid_generate_v7()
RETURNS UUID
AS $$
DECLARE
timestamp TIMESTAMPTZ;
microseconds INT;
BEGIN
timestamp = clock_timestamp();
microseconds = (cast(extract(microseconds FROM timestamp)::INT - (floor(extract(milliseconds FROM timestamp))::INT * 1000) AS DOUBLE PRECISION) * 4.096)::INT;
RETURN encode(
set_byte(
set_byte(
overlay(uuid_send(gen_random_uuid()) placing substring(int8send(floor(extract(epoch FROM timestamp) * 1000)::BIGINT) FROM 3) FROM 1 FOR 6
),
6, (b'0111' || (microseconds >> 8)::bit(4))::bit(8)::int
),
7, microseconds::bit(8)::int
),
'hex')::UUID;
END
$$
LANGUAGE PLPGSQL
VOLATILE;
""")
execute("""
CREATE OR REPLACE FUNCTION timestamp_from_uuid_v7(_uuid uuid)
RETURNS TIMESTAMP WITHOUT TIME ZONE
AS $$
SELECT to_timestamp(('x0000' || substr(_uuid::TEXT, 1, 8) || substr(_uuid::TEXT, 10, 4))::BIT(64)::BIGINT::NUMERIC / 1000);
$$
LANGUAGE SQL
IMMUTABLE PARALLEL SAFE STRICT;
""")
end
def down do
# Uncomment this if you actually want to uninstall the extensions
# when this migration is rolled back:
execute("DROP FUNCTION IF EXISTS uuid_generate_v7(), timestamp_from_uuid_v7(uuid)")
end
end