diff --git a/assets/js/hooks/Mapper/common-styles/fixes.scss b/assets/js/hooks/Mapper/common-styles/fixes.scss index bf5cb4e6..d68345b0 100644 --- a/assets/js/hooks/Mapper/common-styles/fixes.scss +++ b/assets/js/hooks/Mapper/common-styles/fixes.scss @@ -85,3 +85,26 @@ } } +.p-dropdown-label, .p-inputtext { + padding: 0.25rem 0.75rem; + font-size: 14px; +} + +.p-dropdown-item { + padding: 0.25rem 0.5rem; + font-size: 14px; +} + +.p-dropdown-item-group { + padding: 0.25rem 0.75rem; + font-size: 14px; +} + +.p-dropdown-trigger { + width: 14px; + margin: 0 12px; +} + +.p-dropdown-empty-message { + padding: 0.25rem 0.5rem; +} diff --git a/assets/js/hooks/Mapper/components/contexts/ContextMenuSystem/useContextMenuSystemHandlers.ts b/assets/js/hooks/Mapper/components/contexts/ContextMenuSystem/useContextMenuSystemHandlers.ts index a1e366c4..ba1538ea 100644 --- a/assets/js/hooks/Mapper/components/contexts/ContextMenuSystem/useContextMenuSystemHandlers.ts +++ b/assets/js/hooks/Mapper/components/contexts/ContextMenuSystem/useContextMenuSystemHandlers.ts @@ -4,6 +4,7 @@ import { OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers. import { SolarSystemRawType } from '@/hooks/Mapper/types'; import { WaypointSetContextHandler } from '@/hooks/Mapper/components/contexts/types.ts'; import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts'; +import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks'; interface UseContextMenuSystemHandlersProps { hubs: string[]; @@ -16,8 +17,10 @@ export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseC const [system, setSystem] = useState(); - const ref = useRef({ hubs, system, systems, outCommand }); - ref.current = { hubs, system, systems, outCommand }; + const { deleteSystems } = useDeleteSystems(); + + const ref = useRef({ hubs, system, systems, outCommand, deleteSystems }); + ref.current = { hubs, system, systems, outCommand, deleteSystems }; const open = useCallback((ev: any, systemId: string) => { setSystem(systemId); @@ -27,12 +30,12 @@ export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseC }, []); const onDeleteSystem = useCallback(() => { - const { system, outCommand } = ref.current; + const { system, deleteSystems } = ref.current; if (!system) { return; } - outCommand({ type: OutCommand.deleteSystems, data: [system] }); + deleteSystems([system]); setSystem(undefined); }, []); diff --git a/assets/js/hooks/Mapper/components/contexts/ContextMenuSystemInfo/ContextMenuSystemInfo.tsx b/assets/js/hooks/Mapper/components/contexts/ContextMenuSystemInfo/ContextMenuSystemInfo.tsx index ce6b3cc8..6bba61d1 100644 --- a/assets/js/hooks/Mapper/components/contexts/ContextMenuSystemInfo/ContextMenuSystemInfo.tsx +++ b/assets/js/hooks/Mapper/components/contexts/ContextMenuSystemInfo/ContextMenuSystemInfo.tsx @@ -8,7 +8,7 @@ import { getSystemById } from '@/hooks/Mapper/helpers'; import { useWaypointMenu } from '@/hooks/Mapper/components/contexts/hooks'; import { WaypointSetContextHandler } from '@/hooks/Mapper/components/contexts/types.ts'; import { FastSystemActions } from '@/hooks/Mapper/components/contexts/components'; -import { useJumpPlannerMenu } from '@/hooks/Mapper/components/contexts/hooks/useJumpPlannerMenu'; +import { useJumpPlannerMenu } from '@/hooks/Mapper/components/contexts/hooks'; import { Route } from '@/hooks/Mapper/types/routes.ts'; export interface ContextMenuSystemInfoProps { diff --git a/assets/js/hooks/Mapper/components/contexts/ContextMenuSystemMultiple/useContextMenuSystemMultipleHandlers.ts b/assets/js/hooks/Mapper/components/contexts/ContextMenuSystemMultiple/useContextMenuSystemMultipleHandlers.ts index b45b4a57..ffa5105f 100644 --- a/assets/js/hooks/Mapper/components/contexts/ContextMenuSystemMultiple/useContextMenuSystemMultipleHandlers.ts +++ b/assets/js/hooks/Mapper/components/contexts/ContextMenuSystemMultiple/useContextMenuSystemMultipleHandlers.ts @@ -1,17 +1,17 @@ import { Node } from 'reactflow'; -import { useRef, useState } from 'react'; +import { useCallback, useRef, useState } from 'react'; import { ContextMenu } from 'primereact/contextmenu'; -import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts'; import { SolarSystemRawType } from '@/hooks/Mapper/types'; import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts'; -import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; import { NodeSelectionMouseHandler } from '@/hooks/Mapper/components/contexts/types.ts'; +import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks'; export const useContextMenuSystemMultipleHandlers = () => { const contextMenuRef = useRef(null); - const { outCommand } = useMapRootState(); const [systems, setSystems] = useState[]>(); + const { deleteSystems } = useDeleteSystems(); + const handleSystemMultipleContext: NodeSelectionMouseHandler = (ev, systems_) => { setSystems(systems_); ev.preventDefault(); @@ -19,7 +19,7 @@ export const useContextMenuSystemMultipleHandlers = () => { contextMenuRef.current?.show(ev); }; - const onDeleteSystems = () => { + const onDeleteSystems = useCallback(() => { if (!systems) { return; } @@ -29,12 +29,11 @@ export const useContextMenuSystemMultipleHandlers = () => { return; } - outCommand({ type: OutCommand.deleteSystems, data: sysToDel }); - }; + deleteSystems(sysToDel); + }, [deleteSystems, systems]); return { handleSystemMultipleContext, - contextMenuRef, onDeleteSystems, }; diff --git a/assets/js/hooks/Mapper/components/contexts/hooks/index.ts b/assets/js/hooks/Mapper/components/contexts/hooks/index.ts index 1bd51adb..52e7bf78 100644 --- a/assets/js/hooks/Mapper/components/contexts/hooks/index.ts +++ b/assets/js/hooks/Mapper/components/contexts/hooks/index.ts @@ -1 +1,3 @@ export * from './useWaypointMenu'; +export * from './useJumpPlannerMenu'; +export * from './useDeleteSystems'; diff --git a/assets/js/hooks/Mapper/components/contexts/hooks/useDeleteSystems.ts b/assets/js/hooks/Mapper/components/contexts/hooks/useDeleteSystems.ts new file mode 100644 index 00000000..e5253645 --- /dev/null +++ b/assets/js/hooks/Mapper/components/contexts/hooks/useDeleteSystems.ts @@ -0,0 +1,18 @@ +import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts'; +import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; + +export const useDeleteSystems = () => { + const { outCommand } = useMapRootState(); + + const deleteSystems = (systemIds: string[]) => { + if (!systemIds || !systemIds.length) { + return; + } + + outCommand({ type: OutCommand.deleteSystems, data: systemIds }); + }; + + return { + deleteSystems, + }; +}; diff --git a/assets/js/hooks/Mapper/components/hooks/index.ts b/assets/js/hooks/Mapper/components/hooks/index.ts new file mode 100644 index 00000000..8c0c3ab1 --- /dev/null +++ b/assets/js/hooks/Mapper/components/hooks/index.ts @@ -0,0 +1,2 @@ +export * from './useSystemInfo'; +export * from './useGetOwnOnlineCharacters'; diff --git a/assets/js/hooks/Mapper/components/hooks/useSystemInfo.ts b/assets/js/hooks/Mapper/components/hooks/useSystemInfo.ts new file mode 100644 index 00000000..ca639bd8 --- /dev/null +++ b/assets/js/hooks/Mapper/components/hooks/useSystemInfo.ts @@ -0,0 +1,33 @@ +import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; +import { useMemo } from 'react'; +import { getSystemById } from '@/hooks/Mapper/helpers'; +import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts'; + +interface UseSystemInfoProps { + systemId: string; +} + +export const useSystemInfo = ({ systemId }: UseSystemInfoProps) => { + const { + data: { systems, connections }, + } = useMapRootState(); + + const { systems: systemStatics } = useLoadSystemStatic({ systems: [systemId] }); + + return useMemo(() => { + const staticInfo = systemStatics.get(parseInt(systemId)); + const dynamicInfo = getSystemById(systems, systemId); + + if (!staticInfo || !dynamicInfo) { + throw new Error(`Error on getting system ${systemId}`); + } + + const leadsTo = connections + .filter(x => [x.source, x.target].includes(systemId)) + .map(x => [x.source, x.target]) + .flat() + .filter(x => x !== systemId); + + return { dynamicInfo, staticInfo, leadsTo }; + }, [systemStatics, systemId, systems, connections]); +}; diff --git a/assets/js/hooks/Mapper/components/map/Map.tsx b/assets/js/hooks/Mapper/components/map/Map.tsx index 81621c86..5a621649 100644 --- a/assets/js/hooks/Mapper/components/map/Map.tsx +++ b/assets/js/hooks/Mapper/components/map/Map.tsx @@ -1,4 +1,4 @@ -import { ForwardedRef, forwardRef, MouseEvent, useCallback, useEffect } from 'react'; +import { ForwardedRef, forwardRef, MouseEvent, useCallback, useEffect, useRef } from 'react'; import ReactFlow, { Background, ConnectionMode, @@ -13,12 +13,15 @@ import ReactFlow, { SelectionMode, useEdgesState, useNodesState, + NodeChange, + useReactFlow, } from 'reactflow'; import 'reactflow/dist/style.css'; import classes from './Map.module.scss'; import './styles/neon-theme.scss'; import './styles/eve-common.scss'; import { MapProvider, useMapState } from './MapProvider'; +import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; import { useMapHandlers, useUpdateNodes } from './hooks'; import { MapHandlers, OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts'; import { @@ -34,6 +37,7 @@ import { SESSION_KEY } from '@/hooks/Mapper/constants.ts'; import { SolarSystemConnection, SolarSystemRawType } from '@/hooks/Mapper/types'; import { ctxManager } from '@/hooks/Mapper/utils/contextManager.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 }; @@ -108,6 +112,7 @@ const MapComp = ({ isShowMinimap, showKSpaceBG, }: MapCompProps) => { + const { getNode } = useReactFlow(); const [nodes, , onNodesChange] = useNodesState(initialNodes); const [edges, , onEdgesChange] = useEdgesState[]>(initialEdges); @@ -115,8 +120,15 @@ const MapComp = ({ useUpdateNodes(nodes); const { handleRootContext, ...rootCtxProps } = useContextMenuRootHandlers(); const { handleConnectionContext, ...connectionCtxProps } = useContextMenuConnectionHandlers(); - const { update } = useMapState(); + const { + data: { systems }, + } = useMapRootState(); + + const { deleteSystems } = useDeleteSystems(); + + const systemsRef = useRef({ systems }); + systemsRef.current = { systems }; const onConnect: OnConnect = useCallback( params => { @@ -171,6 +183,32 @@ const MapComp = ({ localStorage.setItem(SESSION_KEY.viewPort, JSON.stringify(viewport)); }; + const handleNodesChange = useCallback( + (changes: NodeChange[]) => { + const systemsIdsToRemove: string[] = []; + const nextChanges = changes.reduce((acc, change) => { + if (change.type === 'remove') { + const node = getNode(change.id); + const { systems = [] } = systemsRef.current; + if (node?.data?.id && !systems.map(s => s.id).includes(node?.data?.id)) { + return [...acc, change]; + } else { + systemsIdsToRemove.push(node?.data?.id); + } + return acc; + } + return [...acc, change]; + }, [] as NodeChange[]); + + if (systemsIdsToRemove.length) { + deleteSystems(systemsIdsToRemove); + } + + onNodesChange(nextChanges); + }, + [deleteSystems, getNode, onNodesChange], + ); + useEffect(() => { update(x => ({ ...x, @@ -184,7 +222,7 @@ const MapComp = ({ {isShowMinimap && } diff --git a/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNode.module.scss b/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNode.module.scss index d19e1910..cc73ec94 100644 --- a/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNode.module.scss +++ b/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNode.module.scss @@ -1,4 +1,4 @@ -@import "@/hooks/Mapper/components/map/styles/eve-common-variables"; +@import '@/hooks/Mapper/components/map/styles/eve-common-variables'; $pastel-blue: #5a7d9a; $pastel-pink: #d291bc; @@ -25,9 +25,11 @@ $tooltip-bg: #202020; // Темный фон для подсказок z-index: 1; overflow: hidden; - - &.Mataria, &.Amarria, &.Gallente, &.Caldaria { - &::Before { + &.Mataria, + &.Amarria, + &.Gallente, + &.Caldaria { + &::before { content: ''; position: absolute; top: 0; @@ -44,42 +46,40 @@ $tooltip-bg: #202020; // Темный фон для подсказок &.Mataria { &::before { - background-image: url("/images/mataria.png"); + background-image: url('/images/mataria-180.png'); opacity: 0.6; - background-position-x: -28px; - background-position-y: -3px; + background-position-x: 1px; + background-position-y: -14px; } } &.Caldaria { &::before { - background-image: url("/images/caldaria.png"); + background-image: url('/images/caldaria-180.png'); opacity: 0.6; - background-position-x: -16px; - background-position-y: -17px; + background-position-x: 1px; + background-position-y: -10px; } } &.Amarria { &::before { opacity: 0.45; - background-image: url("/images/amarr.png"); - background-position-x: 0px; - background-position-y: -1px; - width: calc(100% + 10px) + background-image: url('/images/amarr-180.png'); + background-position-x: 0; + background-position-y: -13px; } } &.Gallente { &::before { - opacity: 0.6; - background-image: url("/images/gallente.png"); - background-position-x: -1px; - background-position-y: -10px; + opacity: 0.5; + background-image: url('/images/gallente-180.png'); + background-position-x: 1px; + background-position-y: 0; } } - &.selected { border-color: $pastel-pink; box-shadow: 0 0 10px #9a1af1c2; @@ -95,7 +95,7 @@ $tooltip-bg: #202020; // Темный фон для подсказок &.eve-system-status-home { border: 1px solid darken($eve-solar-system-status-color-home, 30%); - background-image: linear-gradient(45deg, $eve-solar-system-status-friendly, transparent); + background-image: linear-gradient(275deg, $eve-solar-system-status-friendly, transparent); &.selected { border-color: $eve-solar-system-status-color-home; @@ -104,7 +104,7 @@ $tooltip-bg: #202020; // Темный фон для подсказок &.eve-system-status-friendly { border: 1px solid darken($eve-solar-system-status-color-friendly, 20%); - background-image: linear-gradient(45deg, darken($eve-solar-system-status-friendly, 30%), transparent); + background-image: linear-gradient(275deg, darken($eve-solar-system-status-friendly, 30%), transparent); &.selected { border-color: darken($eve-solar-system-status-color-friendly, 5%); @@ -113,7 +113,7 @@ $tooltip-bg: #202020; // Темный фон для подсказок &.eve-system-status-lookingFor { border: 1px solid darken($eve-solar-system-status-color-lookingFor, 15%); - background-image: linear-gradient(45deg, #45ff8f2f, #457fff2f); + background-image: linear-gradient(275deg, #45ff8f2f, #457fff2f); &.selected { border-color: $pastel-pink; @@ -121,17 +121,16 @@ $tooltip-bg: #202020; // Темный фон для подсказок } &.eve-system-status-warning { - background-image: linear-gradient(45deg, $eve-solar-system-status-warning, transparent); + background-image: linear-gradient(275deg, $eve-solar-system-status-warning, transparent); } &.eve-system-status-dangerous { - background-image: linear-gradient(45deg, $eve-solar-system-status-dangerous, transparent); + background-image: linear-gradient(275deg, $eve-solar-system-status-dangerous, transparent); } &.eve-system-status-target { - background-image: linear-gradient(45deg, $eve-solar-system-status-target, transparent); + background-image: linear-gradient(275deg, $eve-solar-system-status-target, transparent); } - } .Bookmarks { @@ -158,7 +157,7 @@ $tooltip-bg: #202020; // Темный фон для подсказок //background-color: #833ca4; &:not(:first-child) { - box-shadow: inset 4px -3px 4px rgba(0, 0, 0, .3); + box-shadow: inset 4px -3px 4px rgba(0, 0, 0, 0.3); } } @@ -181,7 +180,6 @@ $tooltip-bg: #202020; // Темный фон для подсказок font-size: 9px; } } - } .icon { @@ -219,9 +217,7 @@ $tooltip-bg: #202020; // Темный фон для подсказок } .solarSystemName { - } - } .BottomRow { @@ -288,11 +284,19 @@ $tooltip-bg: #202020; // Темный фон для подсказок border-color: $pastel-pink; } - &.HandleTop { top: -2px } + &.HandleTop { + top: -2px; + } - &.HandleRight { right: -2px } + &.HandleRight { + right: -2px; + } - &.HandleBottom { bottom: -2px } + &.HandleBottom { + bottom: -2px; + } - &.HandleLeft { left: -2px } + &.HandleLeft { + left: -2px; + } } diff --git a/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNode.tsx b/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNode.tsx index 316dfb45..7335a575 100644 --- a/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNode.tsx +++ b/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNode.tsx @@ -133,7 +133,7 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps {labelCustom !== '' && (
-
{labelCustom}
+ {labelCustom}
)} @@ -168,14 +168,16 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps
-
{class_title ?? '-'}
+
+ {class_title ?? '-'} +
{tag != null && tag !== '' && (
{tag}
)}
{solar_system_name} @@ -196,16 +198,16 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps {customName && ( -
{customName}
+
+ {customName} +
)} {!isWormhole && !customName && (
{region_name}
@@ -215,10 +217,10 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps
- {locked && } + {locked && } {hubs.includes(solar_system_id.toString()) && ( - + )} {charactersInSystem.length > 0 && ( diff --git a/assets/js/hooks/Mapper/components/map/components/WormholeClassComp/WormholeClassComp.tsx b/assets/js/hooks/Mapper/components/map/components/WormholeClassComp/WormholeClassComp.tsx index 31ed3356..6cc62b79 100644 --- a/assets/js/hooks/Mapper/components/map/components/WormholeClassComp/WormholeClassComp.tsx +++ b/assets/js/hooks/Mapper/components/map/components/WormholeClassComp/WormholeClassComp.tsx @@ -18,5 +18,9 @@ export const WormholeClassComp = ({ id }: WormholeClassComp) => { } const colorClass = WORMHOLE_CLASS_STYLES[wormholeDataAdditional.wormholeClassID.toString()]; - return
{wormholeDataAdditional.shortName}
; + return ( +
+ {wormholeDataAdditional.shortName} +
+ ); }; diff --git a/assets/js/hooks/Mapper/components/map/constants.ts b/assets/js/hooks/Mapper/components/map/constants.ts index 08b5d643..c7dc5efa 100644 --- a/assets/js/hooks/Mapper/components/map/constants.ts +++ b/assets/js/hooks/Mapper/components/map/constants.ts @@ -30,11 +30,77 @@ export enum SOLAR_SYSTEM_CLASS_IDS { zarzakh = 10100, } +export enum SOLAR_SYSTEM_CLASS_GROUPS { + ccp = 'ccp', + c1 = 'c1', + c2 = 'c2', + c3 = 'c3', + c4 = 'c4', + c5 = 'c5', + c6 = 'c6', + hs = 'hs', + ls = 'ls', + ns = 'ns', + thera = 'thera', + c13 = 'c13', + drifter = 'drifter', + unknown = 'unknown', + pochven = 'pochven', + jovian = 'jovian', +} + +export const SOLAR_SYSTEM_TO_CLASS_GROUPS_CLASSES = { + c1: ['c1'], + c2: ['c2'], + c3: ['c3'], + c4: ['c4'], + c5: ['c5'], + c6: ['c6'], + hs: ['hs'], + ls: ['ls'], + ns: ['ns'], + thera: ['thera'], + c13: ['c13'], + pochven: ['pochven'], + drifter: ['sentinel', 'barbican', 'vidette', 'conflux', 'redoubt'], + jove: ['jove'], +}; + +export const SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS = { + ccp1: SOLAR_SYSTEM_CLASS_GROUPS.ccp, + c1: SOLAR_SYSTEM_CLASS_GROUPS.c1, + c2: SOLAR_SYSTEM_CLASS_GROUPS.c2, + c3: SOLAR_SYSTEM_CLASS_GROUPS.c3, + c4: SOLAR_SYSTEM_CLASS_GROUPS.c4, + c5: SOLAR_SYSTEM_CLASS_GROUPS.c5, + c6: SOLAR_SYSTEM_CLASS_GROUPS.c6, + hs: SOLAR_SYSTEM_CLASS_GROUPS.hs, + ls: SOLAR_SYSTEM_CLASS_GROUPS.ls, + ns: SOLAR_SYSTEM_CLASS_GROUPS.ns, + ccp2: SOLAR_SYSTEM_CLASS_GROUPS.ccp, + ccp3: SOLAR_SYSTEM_CLASS_GROUPS.ccp, + thera: SOLAR_SYSTEM_CLASS_GROUPS.thera, + c13: SOLAR_SYSTEM_CLASS_GROUPS.c13, + sentinel: SOLAR_SYSTEM_CLASS_GROUPS.drifter, + baribican: SOLAR_SYSTEM_CLASS_GROUPS.drifter, + vidette: SOLAR_SYSTEM_CLASS_GROUPS.drifter, + conflux: SOLAR_SYSTEM_CLASS_GROUPS.drifter, + redoubt: SOLAR_SYSTEM_CLASS_GROUPS.drifter, + a1: SOLAR_SYSTEM_CLASS_GROUPS.unknown, + a2: SOLAR_SYSTEM_CLASS_GROUPS.unknown, + a3: SOLAR_SYSTEM_CLASS_GROUPS.unknown, + a4: SOLAR_SYSTEM_CLASS_GROUPS.unknown, + a5: SOLAR_SYSTEM_CLASS_GROUPS.unknown, + ccp4: SOLAR_SYSTEM_CLASS_GROUPS.ccp, + pochven: SOLAR_SYSTEM_CLASS_GROUPS.pochven, +}; + type WormholesAdditionalInfoType = { id: string; shortName: string; wormholeClassID: number; title: string; + shortTitle: string; effectPower?: number; }; @@ -45,6 +111,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [ shortName: 'CCP', wormholeClassID: -1, title: 'CCP System', + shortTitle: 'CCP', }, { id: 'c1', @@ -52,6 +119,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [ wormholeClassID: 1, effectPower: 1, title: 'Class 1', + shortTitle: 'C1', }, { id: 'c2', @@ -59,6 +127,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [ wormholeClassID: 2, effectPower: 2, title: 'Class 2', + shortTitle: 'C2', }, { id: 'c3', @@ -66,6 +135,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [ wormholeClassID: 3, effectPower: 3, title: 'Class 3', + shortTitle: 'C3', }, { id: 'c4', @@ -73,6 +143,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [ wormholeClassID: 4, effectPower: 4, title: 'Class 4', + shortTitle: 'C4', }, { id: 'c5', @@ -80,6 +151,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [ wormholeClassID: 5, effectPower: 5, title: 'Class 5', + shortTitle: 'C5', }, { id: 'c6', @@ -87,42 +159,49 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [ wormholeClassID: 6, effectPower: 6, title: 'Class 6', + shortTitle: 'C6', }, { id: 'hs', shortName: 'H', wormholeClassID: 7, title: 'High-sec', + shortTitle: 'High-sec', }, { id: 'ls', shortName: 'L', wormholeClassID: 8, title: 'Low-sec', + shortTitle: 'Low-sec', }, { id: 'ns', shortName: 'N', wormholeClassID: 9, title: 'Null-sec', + shortTitle: 'Null-sec', }, { id: 'ccp2', shortName: 'CCP', wormholeClassID: 10, title: 'CCP System', + shortTitle: 'CCP', }, { id: 'ccp3', shortName: 'CCP', wormholeClassID: 11, title: 'CCP System', + shortTitle: 'CCP', }, { id: 'thera', shortName: 'T', wormholeClassID: 12, title: 'Class 12 (Thera)', + shortTitle: 'Thera', }, { id: 'c13', @@ -130,6 +209,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [ wormholeClassID: 13, effectPower: 6, title: 'Class 13 (Shattered Frigate)', + shortTitle: 'C13', }, { id: 'sentinel', @@ -137,6 +217,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [ wormholeClassID: 14, effectPower: 2, title: 'Class 14 (Sentinel Drifter)', + shortTitle: 'Sentinel', }, { id: 'barbican', @@ -144,6 +225,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [ wormholeClassID: 15, effectPower: 2, title: 'Class 15 (Barbican Drifter)', + shortTitle: 'Barbican', }, { id: 'vidette', @@ -151,6 +233,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [ wormholeClassID: 16, effectPower: 2, title: 'Class 16 (Vidette Drifter)', + shortTitle: 'Vidette', }, { id: 'conflux', @@ -158,6 +241,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [ wormholeClassID: 17, effectPower: 2, title: 'Class 17 (Conflux Drifter)', + shortTitle: 'Conflux', }, { id: 'redoubt', @@ -165,59 +249,79 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [ wormholeClassID: 18, effectPower: 2, title: 'Class 18 (Redoubt Drifter)', + shortTitle: 'Redoubt', }, { id: 'a1', shortName: 'A1', wormholeClassID: 19, title: '(Abyssal class 1)', + shortTitle: 'A1', }, { id: 'a2', shortName: 'A2', wormholeClassID: 20, title: '(Abyssal class 2)', + shortTitle: 'A2', }, { id: 'a3', shortName: 'A3', wormholeClassID: 21, title: '(Abyssal class 3)', + shortTitle: 'A3', }, { id: 'a4', shortName: 'A4', wormholeClassID: 22, title: '(Abyssal class 4)', + shortTitle: 'A4', }, { id: 'a5', shortName: 'A5', wormholeClassID: 23, title: '(Abyssal class 5)', + shortTitle: 'A5', }, { id: 'ccp4', shortName: 'CCP', wormholeClassID: 24, title: 'CCP System (Penalty)', + shortTitle: 'CCP', }, { id: 'pochven', shortName: 'P', wormholeClassID: 25, title: 'Triglavian space (Pochven)', + shortTitle: 'Pochven', }, { id: 'zarzakh', shortName: 'N', wormholeClassID: 10100, title: 'Pirate space', + shortTitle: 'Zarzakh', + }, + { + id: 'k162', + shortName: 'K162', + wormholeClassID: 10101, + title: 'Reverse', + shortTitle: 'K162', }, ]; + export const WORMHOLES_ADDITIONAL_INFO: Record = WORMHOLES_ADDITIONAL_INFO_RAW.reduce((acc, x) => ({ ...acc, [x.id]: x }), {}); +export const WORMHOLES_ADDITIONAL_INFO_BY_CLASS_ID: Record = + WORMHOLES_ADDITIONAL_INFO_RAW.reduce((acc, x) => ({ ...acc, [x.wormholeClassID]: x }), {}); + // export const SOLAR_SYSTEM_CLASS_NAMES = { // ccp1 = , // c1 = , diff --git a/assets/js/hooks/Mapper/components/map/hooks/useMapHandlers.ts b/assets/js/hooks/Mapper/components/map/hooks/useMapHandlers.ts index c4f35c92..64e35e57 100644 --- a/assets/js/hooks/Mapper/components/map/hooks/useMapHandlers.ts +++ b/assets/js/hooks/Mapper/components/map/hooks/useMapHandlers.ts @@ -18,6 +18,9 @@ import { CommandUpdateSystems, MapHandlers, } from '@/hooks/Mapper/types/mapHandlers.ts'; + +import { useMapEventListener } from '@/hooks/Mapper/events'; + import { useCommandsCharacters, useCommandsConnections, @@ -57,16 +60,13 @@ export const useMapHandlers = (ref: ForwardedRef, onSelectionChange mapInit(data as CommandInit); break; case Commands.addSystems: - mapAddSystems(data as CommandAddSystems); break; case Commands.updateSystems: mapUpdateSystems(data as CommandUpdateSystems); break; case Commands.removeSystems: - removeSystems(data as CommandRemoveSystems); break; case Commands.addConnections: - addConnections(data as CommandAddConnections); break; case Commands.removeConnections: removeConnections(data as CommandRemoveConnections); @@ -131,4 +131,20 @@ export const useMapHandlers = (ref: ForwardedRef, 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; + } + }); }; diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemInfo/SystemInfoContent/SystemInfoContent.tsx b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemInfo/SystemInfoContent/SystemInfoContent.tsx index 84723177..02868212 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemInfo/SystemInfoContent/SystemInfoContent.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemInfo/SystemInfoContent/SystemInfoContent.tsx @@ -13,9 +13,10 @@ export const SystemInfoContent = ({ systemId }: SystemInfoContentProps) => { data: { systems, wormholesData }, } = useMapRootState(); - const sys = getSystemById(systems, systemId)!; + const sys = getSystemById(systems, systemId)! || {}; const { description } = sys; - const { system_class, region_name, constellation_name, statics, effect_name, effect_power } = sys.system_static_info; + const { system_class, region_name, constellation_name, statics, effect_name, effect_power } = + sys.system_static_info || {}; const isWH = isWormholeSpace(system_class); const sortedStatics = useMemo(() => sortWHClasses(wormholesData, statics), [wormholesData, statics]); diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignaturesContent/SystemSignaturesContent.tsx b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignaturesContent/SystemSignaturesContent.tsx index a7055c96..2b34f044 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignaturesContent/SystemSignaturesContent.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignaturesContent/SystemSignaturesContent.tsx @@ -1,10 +1,10 @@ import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; import { useClipboard } from '@/hooks/Mapper/hooks/useClipboard'; import { parseSignatures } from '@/hooks/Mapper/helpers'; -import { 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 { DataTable, DataTableRowMouseEvent, SortOrder } from 'primereact/datatable'; +import { DataTable, DataTableRowClickEvent, DataTableRowMouseEvent, SortOrder } from 'primereact/datatable'; import { Column } from 'primereact/column'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import useRefState from 'react-usestateref'; @@ -22,12 +22,14 @@ import { } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/helpers'; import { renderIcon, - renderName, + renderInfoColumn, renderTimeLeft, - renderLinkedSystem, } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders'; -// import { PrimeIcons } from 'primereact/api'; import useLocalStorageState from 'use-local-storage-state'; +import { PrimeIcons } from 'primereact/api'; +import { SignatureSettings } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings'; +import { useMapEventListener } from '@/hooks/Mapper/events'; +import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper'; type SystemSignaturesSortSettings = { sortField: string; @@ -53,6 +55,7 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele const [nameColumnWidth, setNameColumnWidth] = useState('auto'); const [parsedSignatures, setParsedSignatures] = useState([]); const [askUser, setAskUser] = useState(false); + const [selectedSignature, setSelectedSignature] = useState(null); const [hoveredSig, setHoveredSig] = useState(null); @@ -164,6 +167,8 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele }, [parsedSignatures, handleUpdateSignatures]); const handleSelectSignatures = useCallback( + // TODO still will be good to define types if we use typescript + // @ts-ignore e => { if (selectable) { onSelect?.(e.value); @@ -212,6 +217,18 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele handleGetSignatures(); }, [systemId]); + useMapEventListener(event => { + switch (event.name) { + case Commands.signaturesUpdated: + if (event.data?.toString() !== systemId.toString()) { + return; + } + + handleGetSignatures(); + return true; + } + }); + useEffect(() => { const observer = new ResizeObserver(handleResize); if (tableRef.current) { @@ -240,13 +257,22 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele setHoveredSig(null); }, []); - // const renderToolbar = (/*row: SystemSignature*/) => { - // return ( - //
- // - //
- // ); - // }; + const renderToolbar = (/*row: SystemSignature*/) => { + return ( +
+ + + +
+ ); + }; + + const [showSignatureSettings, setShowSignatureSettings] = useState(false); + + const handleRowClick = (e: DataTableRowClickEvent) => { + setSelectedSignature(e.data as SystemSignature); + setShowSignatureSettings(true); + }; return ( <> @@ -257,6 +283,7 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele
) : ( <> + {/* @ts-ignore */} renderIcon(x)} style={{ maxWidth: 26, minWidth: 26, width: 26, height: 25 }} > @@ -310,41 +338,29 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele sortable > - - - {/**/} + )} @@ -353,6 +369,14 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele ref={tooltipRef} content={hoveredSig ? : null} /> + + setShowSignatureSettings(false)} + signatureData={selectedSignature} + /> + {askUser && (
diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders/index.ts b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders/index.ts index 73d26aef..b4d46062 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders/index.ts +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders/index.ts @@ -2,3 +2,4 @@ export * from './renderIcon'; export * from './renderName'; export * from './renderTimeLeft'; export * from './renderLinkedSystem'; +export * from './renderInfoColumn'; diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders/renderIcon.tsx b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders/renderIcon.tsx index 1a66b35c..14c56949 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders/renderIcon.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders/renderIcon.tsx @@ -1,7 +1,7 @@ -import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types'; +import { GroupType, SignatureGroup, SystemSignature } from '@/hooks/Mapper/types'; import { GROUPS } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts'; -export const renderIcon = (row: SystemSignature) => { +export const renderIcon = (row: SystemSignature, customSize?: Omit) => { if (row.group == null) { return null; } @@ -13,7 +13,7 @@ export const renderIcon = (row: SystemSignature) => { return (
- +
); }; diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders/renderInfoColumn.module.scss b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders/renderInfoColumn.module.scss new file mode 100644 index 00000000..fea0bc4d --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders/renderInfoColumn.module.scss @@ -0,0 +1,3 @@ +.whFontSize { + font-size: 11px !important; +} diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders/renderInfoColumn.tsx b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders/renderInfoColumn.tsx new file mode 100644 index 00000000..e64e83d3 --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders/renderInfoColumn.tsx @@ -0,0 +1,44 @@ +import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types'; +import { SystemViewStandalone, WHClassView } from '@/hooks/Mapper/components/ui-kit'; +import clsx from 'clsx'; +import { renderName } from './renderName.tsx'; +import classes from './renderInfoColumn.module.scss'; + +export const renderInfoColumn = (row: SystemSignature) => { + if (!row.group || row.group === SignatureGroup.Wormhole) { + return ( +
+ {row.type && ( + + )} + + {row.linked_system && ( + <> + {/**/} + + + + + )} +
+ ); + } + + if (row.description != null && row.description.length > 0) { + return {row.description}; + } + + return renderName(row); +}; diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/Provider.tsx b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/Provider.tsx new file mode 100644 index 00000000..850fd70a --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/Provider.tsx @@ -0,0 +1,10 @@ +import { createGenericContext } from '@/hooks/Mapper/utils/abstractContextProvider.tsx'; + +export interface SystemsSettingsProvider { + systemId: string; +} + +const { Provider, useContextValue } = createGenericContext(); + +export const SystemsSettingsProvider = Provider; +export const useSystemsSettingsProvider = useContextValue; diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.module.scss b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.module.scss new file mode 100644 index 00000000..961635fc --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.module.scss @@ -0,0 +1,81 @@ +.verticalTabsContainer { + width: 100%; + min-height: 300px; +} + +.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: 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 { + border-right: 4px solid var(--primary-color); + } + + } + } + } + + .p-tabview-panel { + flex-grow: 1; + } + } +} diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx new file mode 100644 index 00000000..d692d6a9 --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx @@ -0,0 +1,171 @@ +import { Dialog } from 'primereact/dialog'; +import { useCallback, useEffect } from 'react'; +// import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; +import { OutCommand, SignatureGroup, SystemSignature } from '@/hooks/Mapper/types'; +import { Controller, FormProvider, useForm } from 'react-hook-form'; +import { + SignatureGroupContent, + SignatureGroupSelect, +} from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components'; +import { InputText } from 'primereact/inputtext'; +import { SystemsSettingsProvider } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/Provider.tsx'; +import { Button } from 'primereact/button'; +import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; + +type SystemSignaturePrepared = Omit & { linked_system: string }; + +export interface MapSettingsProps { + systemId: string; + show: boolean; + onHide: () => void; + signatureData: SystemSignature | null; +} + +export const SignatureSettings = ({ systemId, show, onHide, signatureData }: MapSettingsProps) => { + const { outCommand } = useMapRootState(); + + const handleShow = async () => {}; + const form = useForm>({}); + + const handleSave = useCallback(async () => { + if (!signatureData) { + return; + } + + const { group, ...values } = form.getValues(); + let out = { ...signatureData }; + + switch (group) { + case SignatureGroup.Wormhole: + if (values.linked_system) { + await outCommand({ + type: OutCommand.linkSignatureToSystem, + data: { + signature_eve_id: signatureData.eve_id, + solar_system_source: systemId, + solar_system_target: values.linked_system, + }, + }); + } + + if (values.type != null) { + out = { ...out, type: values.type }; + } + + if (signatureData.group !== SignatureGroup.Wormhole) { + out = { ...out, name: '' }; + } + + break; + case SignatureGroup.CosmicSignature: + out = { ...out, type: '', name: '' }; + break; + default: + if (values.name != null) { + out = { ...out, name: values.name ?? '' }; + } + } + + if (values.description != null) { + out = { ...out, description: values.description }; + } + + // Note: when type of signature changed from WH to other type - we should drop name + if ( + group !== SignatureGroup.Wormhole && // new + signatureData.group === SignatureGroup.Wormhole && // prev + signatureData.linked_system + ) { + await outCommand({ + type: OutCommand.unlinkSignature, + data: { signature_eve_id: signatureData.eve_id, solar_system_source: systemId }, + }); + + out = { ...out, type: '' }; + } + + if (group === SignatureGroup.Wormhole && signatureData.linked_system != null && values.linked_system === null) { + await outCommand({ + type: OutCommand.unlinkSignature, + data: { signature_eve_id: signatureData.eve_id, solar_system_source: systemId }, + }); + } + + // Note: despite groups have optional type - this will always set + out = { ...out, group: group! }; + + await outCommand({ + type: OutCommand.updateSignatures, + data: { + system_id: systemId, + added: [], + updated: [out], + removed: [], + }, + }); + + form.reset(); + onHide(); + }, [form, onHide, outCommand, signatureData, systemId]); + + useEffect(() => { + if (!signatureData) { + form.reset(); + return; + } + + const { linked_system, ...rest } = signatureData; + + form.reset({ + linked_system: linked_system?.solar_system_id.toString() ?? undefined, + ...rest, + }); + }, [form, signatureData]); + + return ( + { + if (!show) { + return; + } + + onHide(); + }} + > + + +
+
+ + + + + +
+ +
+ +
+
+
+
+
+ ); +}; diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureGroupContent/SignatureGroupContent.tsx b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureGroupContent/SignatureGroupContent.tsx new file mode 100644 index 00000000..4d05b191 --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureGroupContent/SignatureGroupContent.tsx @@ -0,0 +1,45 @@ +import { Controller, useFormContext } from 'react-hook-form'; +import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types'; +import { useSystemsSettingsProvider } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/Provider.tsx'; +import { SignatureGroupContentWormholes } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureGroupContentWormholes.tsx'; +import { InputText } from 'primereact/inputtext'; + +export interface SignatureGroupContentProps {} + +export const SignatureGroupContent = ({}: SignatureGroupContentProps) => { + const { watch, control } = useFormContext(); + const group = watch('group'); + + const { + value: { systemId }, + } = useSystemsSettingsProvider(); + + if (!systemId) { + return null; + } + + if (group === SignatureGroup.Wormhole) { + return ( + <> + + + ); + } + + if (group === SignatureGroup.CosmicSignature) { + return
; + } + + return ( +
+ +
+ ); +}; diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureGroupContent/index.ts b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureGroupContent/index.ts new file mode 100644 index 00000000..70271a1f --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureGroupContent/index.ts @@ -0,0 +1 @@ +export * from './SignatureGroupContent'; diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureGroupContentWormholes.tsx b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureGroupContentWormholes.tsx new file mode 100644 index 00000000..3ab9f05b --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureGroupContentWormholes.tsx @@ -0,0 +1,18 @@ +import { SignatureWormholeTypeSelect } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureWormholeTypeSelect'; +import { SignatureLeadsToSelect } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureLeadsToSelect'; + +export const SignatureGroupContentWormholes = () => { + return ( + <> + + + + + ); +}; diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureGroupSelect/SignatureGroupSelect.tsx b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureGroupSelect/SignatureGroupSelect.tsx new file mode 100644 index 00000000..94d82406 --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureGroupSelect/SignatureGroupSelect.tsx @@ -0,0 +1,59 @@ +import { Dropdown } from 'primereact/dropdown'; +import clsx from 'clsx'; +import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types'; +import { renderIcon } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders'; +import { Controller, useFormContext } from 'react-hook-form'; + +const signatureGroupOptions = Object.keys(SignatureGroup).map(x => ({ + value: SignatureGroup[x as keyof typeof SignatureGroup], + label: SignatureGroup[x as keyof typeof SignatureGroup], +})); + +// @ts-ignore +const renderSignatureTemplate = option => { + if (!option) { + return 'No group selected'; + } + + return ( +
+ + {renderIcon( + { group: option.label } as SystemSignature, + option.label === SignatureGroup.CosmicSignature ? { w: 10, h: 10 } : { w: 16, h: 16 }, + )} + + {option.label} +
+ ); +}; + +export interface SignatureGroupSelectProps { + name: string; + defaultValue?: string; +} + +export const SignatureGroupSelect = ({ name, defaultValue = '' }: SignatureGroupSelectProps) => { + const { control } = useFormContext(); + return ( + ( + + )} + /> + ); +}; diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureGroupSelect/index.ts b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureGroupSelect/index.ts new file mode 100644 index 00000000..6e44ad50 --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureGroupSelect/index.ts @@ -0,0 +1 @@ +export * from './SignatureGroupSelect'; diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureLeadsToSelect/SignatureLeadsToSelect.module.scss b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureLeadsToSelect/SignatureLeadsToSelect.module.scss new file mode 100644 index 00000000..957fae94 --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureLeadsToSelect/SignatureLeadsToSelect.module.scss @@ -0,0 +1,3 @@ +.SystemView { + font-size: 14px !important; +} diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureLeadsToSelect/SignatureLeadsToSelect.tsx b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureLeadsToSelect/SignatureLeadsToSelect.tsx new file mode 100644 index 00000000..c5b2e611 --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureLeadsToSelect/SignatureLeadsToSelect.tsx @@ -0,0 +1,108 @@ +import { Dropdown } from 'primereact/dropdown'; +import clsx from 'clsx'; +import { Controller, useFormContext } from 'react-hook-form'; +import { useSystemsSettingsProvider } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/Provider.tsx'; +import { useSystemInfo } from '@/hooks/Mapper/components/hooks'; +import { useMemo } from 'react'; +import { SystemView } from '@/hooks/Mapper/components/ui-kit'; +import classes from './SignatureLeadsToSelect.module.scss'; +import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts'; +import { SystemSignature } from '@/hooks/Mapper/types'; +import { WORMHOLES_ADDITIONAL_INFO_BY_CLASS_ID } from '@/hooks/Mapper/components/map/constants.ts'; +import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; + +// @ts-ignore +const renderLinkedSystemItem = (option: { value: string }) => { + if (option.value == null) { + return
No linked system
; + } + + return ( +
+ +
+ ); +}; + +// @ts-ignore +const renderLinkedSystemValue = (option: { value: string }) => { + if (!option) { + return 'Select Leads To system'; + } + + if (option.value == null) { + return 'Select Leads To system'; + } + + return ( +
+ +
+ ); +}; + +const renderLeadsToEmpty = () =>
No wormhole to select
; + +export interface SignatureLeadsToSelectProps { + name: string; + defaultValue?: string; +} + +export const SignatureLeadsToSelect = ({ name, defaultValue = '' }: SignatureLeadsToSelectProps) => { + const { control, watch } = useFormContext(); + const group = watch('type'); + + const { + value: { systemId }, + } = useSystemsSettingsProvider(); + + const { leadsTo } = useSystemInfo({ systemId }); + const { systems: systemStatics } = useLoadSystemStatic({ systems: leadsTo }); + const { + data: { wormholes }, + } = useMapRootState(); + + const leadsToOptions = useMemo(() => { + return [ + { value: null }, + ...leadsTo + .filter(systemId => { + const systemStatic = systemStatics.get(parseInt(systemId)); + const whInfo = wormholes.find(x => x.name === group); + + if (!systemStatic || !whInfo || group === 'K162') { + return true; + } + + const { id: whType } = WORMHOLES_ADDITIONAL_INFO_BY_CLASS_ID[systemStatic.system_class]; + return whInfo.dest === whType; + }) + .map(x => ({ value: x })), + ]; + }, [group, leadsTo, systemStatics, wormholes]); + + return ( + { + return ( + + ); + }} + /> + ); +}; diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureLeadsToSelect/index.ts b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureLeadsToSelect/index.ts new file mode 100644 index 00000000..8997b0da --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureLeadsToSelect/index.ts @@ -0,0 +1 @@ +export * from './SignatureLeadsToSelect.tsx'; diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureWormholeTypeSelect/SignatureWormholeTypeSelect.tsx b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureWormholeTypeSelect/SignatureWormholeTypeSelect.tsx new file mode 100644 index 00000000..77bd1c66 --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureWormholeTypeSelect/SignatureWormholeTypeSelect.tsx @@ -0,0 +1,134 @@ +import { Dropdown } from 'primereact/dropdown'; +import clsx from 'clsx'; +import { Respawn, SolarSystemStaticInfoRaw, WormholeDataRaw } from '@/hooks/Mapper/types'; +import { Controller, useFormContext } from 'react-hook-form'; +import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; +import { useSystemsSettingsProvider } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/Provider.tsx'; +import { useSystemInfo } from '@/hooks/Mapper/components/hooks'; +import { + SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS, + WORMHOLES_ADDITIONAL_INFO_BY_CLASS_ID, +} from '@/hooks/Mapper/components/map/constants.ts'; +import { useMemo } from 'react'; +import { WHClassView } from '@/hooks/Mapper/components/ui-kit'; + +const getPossibleWormholes = (systemStatic: SolarSystemStaticInfoRaw, wormholes: WormholeDataRaw[]) => { + const { id: whType } = WORMHOLES_ADDITIONAL_INFO_BY_CLASS_ID[systemStatic.system_class]; + + // @ts-ignore + const spawnClassGroup = SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS[whType]; + const possibleWHTypes = wormholes.filter(x => x.src.includes(spawnClassGroup)); + + return { + statics: possibleWHTypes + .filter(x => x.respawn.some(y => y === Respawn.static)) + .filter(x => systemStatic.statics.includes(x.name)), + k162: wormholes.find(x => x.name === 'K162')!, + wanderings: possibleWHTypes.filter(x => x.respawn.some(y => y === Respawn.wandering)), + }; +}; + +// @ts-ignore +const renderWHTypeGroupTemplate = option => { + return ( +
+ {option.label} +
+ ); +}; + +// @ts-ignore +const renderWHTypeTemplateValue = (option: { label: string; data: WormholeDataRaw }) => { + if (!option) { + return 'Select wormhole type'; + } + + return ( +
+ +
+ ); +}; + +// @ts-ignore +const renderWHTypeTemplate = (option: { label: string; data: WormholeDataRaw }) => { + return ( +
+ +
+ ); +}; + +export interface SignatureGroupSelectProps { + name: string; + defaultValue?: string; +} + +export const SignatureWormholeTypeSelect = ({ name, defaultValue = '' }: SignatureGroupSelectProps) => { + const { control } = useFormContext(); + + const { + data: { wormholes }, + } = useMapRootState(); + + const { + value: { systemId }, + } = useSystemsSettingsProvider(); + + const system = useSystemInfo({ systemId }); + + const possibleWormholesOptions = useMemo(() => { + const possibleWormholes = getPossibleWormholes(system.staticInfo, wormholes); + + return [ + { + label: 'Statics', + items: [ + ...possibleWormholes.statics.map(x => ({ + label: x.name, + value: x.name, + data: x, + })), + { + value: possibleWormholes.k162.name, + label: possibleWormholes.k162.name, + data: possibleWormholes.k162, + }, + ], + }, + { + label: 'Wanderings', + items: possibleWormholes.wanderings.map(x => ({ + label: x.name, + value: x.name, + data: x, + })), + }, + ]; + }, [system, wormholes]); + + return ( + ( + + )} + /> + ); +}; diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureWormholeTypeSelect/index.ts b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureWormholeTypeSelect/index.ts new file mode 100644 index 00000000..6dd288f8 --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureWormholeTypeSelect/index.ts @@ -0,0 +1 @@ +export * from './SignatureWormholeTypeSelect'; diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/index.ts b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/index.ts new file mode 100644 index 00000000..079ca247 --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/index.ts @@ -0,0 +1,2 @@ +export * from './SignatureGroupSelect'; +export * from './SignatureGroupContent'; diff --git a/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/index.ts b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/index.ts new file mode 100644 index 00000000..5661a934 --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/index.ts @@ -0,0 +1 @@ +export * from './SignatureSettings.tsx'; diff --git a/assets/js/hooks/Mapper/components/ui-kit/WHClassView/WHClassView.module.scss b/assets/js/hooks/Mapper/components/ui-kit/WHClassView/WHClassView.module.scss index 7b212045..11b1cf2c 100644 --- a/assets/js/hooks/Mapper/components/ui-kit/WHClassView/WHClassView.module.scss +++ b/assets/js/hooks/Mapper/components/ui-kit/WHClassView/WHClassView.module.scss @@ -5,6 +5,11 @@ .WHClassViewContent { display: flex; gap: 2px; + + &.NoOffset { + gap: 4px; + align-items: center; + } } .WHClassName { @@ -13,3 +18,12 @@ font-weight: bold; top: -2px; } + +.NoOffset { + *.WHClassName { + position: relative; + font-size: 12px; + font-weight: initial !important; + top: initial !important; + } +} diff --git a/assets/js/hooks/Mapper/components/ui-kit/WHClassView/WHClassView.tsx b/assets/js/hooks/Mapper/components/ui-kit/WHClassView/WHClassView.tsx index b898293d..afae9eb8 100644 --- a/assets/js/hooks/Mapper/components/ui-kit/WHClassView/WHClassView.tsx +++ b/assets/js/hooks/Mapper/components/ui-kit/WHClassView/WHClassView.tsx @@ -16,26 +16,42 @@ const prepareMass = (mass: number) => { export interface WHClassViewProps { whClassName: string; + noOffset?: boolean; + useShortTitle?: boolean; + hideWhClass?: boolean; + highlightName?: boolean; + className?: string; + classNameWh?: string; } -export const WHClassView = ({ whClassName }: WHClassViewProps) => { +export const WHClassView = ({ + whClassName, + noOffset, + useShortTitle, + hideWhClass, + highlightName, + className, + classNameWh, +}: WHClassViewProps) => { const { data: { wormholesData }, } = useMapRootState(); const whData = useMemo(() => wormholesData[whClassName], [whClassName, wormholesData]); const whClass = useMemo(() => WORMHOLES_ADDITIONAL_INFO[whData.dest], [whData.dest]); - const whClassStyle = WORMHOLE_CLASS_STYLES[whClass.wormholeClassID]; + const whClassStyle = WORMHOLE_CLASS_STYLES[whClass?.wormholeClassID] ?? ''; + + const uid = useMemo(() => new Date().getTime().toString(), []); return ( -
+
@@ -49,9 +65,20 @@ export const WHClassView = ({ whClassName }: WHClassViewProps) => {
-
- {whClassName} - {whClass.shortName} +
+ {whClassName} + {!hideWhClass && whClass && ( + + {useShortTitle ? whClass.shortTitle : whClass.shortName} + + )}
); diff --git a/assets/js/hooks/Mapper/events/index.ts b/assets/js/hooks/Mapper/events/index.ts index c9aef7ae..e3a5f8ed 100644 --- a/assets/js/hooks/Mapper/events/index.ts +++ b/assets/js/hooks/Mapper/events/index.ts @@ -1,13 +1,12 @@ import { createEvent } from 'react-event-hook'; -export interface MapEvent { - name: string; - data: { - solar_system_source: number; - solar_system_target: number; - }; +import { Command, CommandData } from '@/hooks/Mapper/types/mapHandlers.ts'; + +export interface MapEvent { + name: T; + data: CommandData[T]; } -const { useMapEventListener, emitMapEvent } = createEvent('map-event')(); +const { useMapEventListener, emitMapEvent } = createEvent('map-event')>(); export { useMapEventListener, emitMapEvent }; diff --git a/assets/js/hooks/Mapper/helpers/parseSignatures.ts b/assets/js/hooks/Mapper/helpers/parseSignatures.ts index c4ee49d7..5626ae54 100644 --- a/assets/js/hooks/Mapper/helpers/parseSignatures.ts +++ b/assets/js/hooks/Mapper/helpers/parseSignatures.ts @@ -19,6 +19,7 @@ export const parseSignatures = (value: string, availableKeys: string[]): SystemS kind: availableKeys.includes(sigArrInfo[1]) ? sigArrInfo[1] : COSMIC_SIGNATURE, group: sigArrInfo[2], name: sigArrInfo[3], + type: '', }); } diff --git a/assets/js/hooks/Mapper/helpers/sortWHClasses.ts b/assets/js/hooks/Mapper/helpers/sortWHClasses.ts index ae27dd5d..becf44ed 100644 --- a/assets/js/hooks/Mapper/helpers/sortWHClasses.ts +++ b/assets/js/hooks/Mapper/helpers/sortWHClasses.ts @@ -2,6 +2,10 @@ import { WORMHOLES_ADDITIONAL_INFO } from '@/hooks/Mapper/components/map/constan import { WormholeDataRaw } from '@/hooks/Mapper/types'; export const sortWHClasses = (wormholesData: Record, statics: string[]) => { + if (!statics) { + return []; + } + return statics .map(x => wormholesData[x]) .map(x => ({ name: x.name, ...WORMHOLES_ADDITIONAL_INFO[x.dest] })) diff --git a/assets/js/hooks/Mapper/mapRootProvider/MapRootProvider.tsx b/assets/js/hooks/Mapper/mapRootProvider/MapRootProvider.tsx index 6492ae26..5dab427b 100644 --- a/assets/js/hooks/Mapper/mapRootProvider/MapRootProvider.tsx +++ b/assets/js/hooks/Mapper/mapRootProvider/MapRootProvider.tsx @@ -12,6 +12,7 @@ export type MapRootData = MapUnionTypes & { const INITIAL_DATA: MapRootData = { wormholesData: {}, + wormholes: [], effects: {}, characters: [], userCharacters: [], diff --git a/assets/js/hooks/Mapper/mapRootProvider/hooks/api/useMapInit.ts b/assets/js/hooks/Mapper/mapRootProvider/hooks/api/useMapInit.ts index 186e592b..d2bfad9a 100644 --- a/assets/js/hooks/Mapper/mapRootProvider/hooks/api/useMapInit.ts +++ b/assets/js/hooks/Mapper/mapRootProvider/hooks/api/useMapInit.ts @@ -24,6 +24,7 @@ export const useMapInit = () => { if (wormholes) { updateData.wormholesData = wormholes.reduce((acc, x) => ({ ...acc, [x.name]: x }), {}); + updateData.wormholes = wormholes; } if (effects) { diff --git a/assets/js/hooks/Mapper/mapRootProvider/hooks/useMapRootHandlers.ts b/assets/js/hooks/Mapper/mapRootProvider/hooks/useMapRootHandlers.ts index 67368813..36cb5827 100644 --- a/assets/js/hooks/Mapper/mapRootProvider/hooks/useMapRootHandlers.ts +++ b/assets/js/hooks/Mapper/mapRootProvider/hooks/useMapRootHandlers.ts @@ -49,15 +49,25 @@ export const useMapRootHandlers = (ref: ForwardedRef) => { break; case Commands.addSystems: addSystems(data as CommandAddSystems); + setTimeout(() => { + emitMapEvent({ name: Commands.addSystems, data }); + }, 100); break; case Commands.updateSystems: updateSystems(data as CommandUpdateSystems); break; case Commands.removeSystems: removeSystems(data as CommandRemoveSystems); + setTimeout(() => { + emitMapEvent({ name: Commands.removeSystems, data }); + }, 100); + break; case Commands.addConnections: addConnections(data as CommandAddConnections); + setTimeout(() => { + emitMapEvent({ name: Commands.addConnections, data }); + }, 100); break; case Commands.removeConnections: removeConnections(data as CommandRemoveConnections); @@ -96,6 +106,8 @@ export const useMapRootHandlers = (ref: ForwardedRef) => { break; case Commands.linkSignatureToSystem: + // TODO command data type lost + // @ts-ignore emitMapEvent({ name: Commands.linkSignatureToSystem, data }); break; @@ -103,6 +115,12 @@ export const useMapRootHandlers = (ref: ForwardedRef) => { // do nothing here break; + case Commands.signaturesUpdated: + // TODO command data type lost + // @ts-ignore + emitMapEvent({ name: Commands.signaturesUpdated, data }); + break; + default: console.warn(`JOipP Interface handlers: Unknown command: ${type}`, data); break; diff --git a/assets/js/hooks/Mapper/types/mapHandlers.ts b/assets/js/hooks/Mapper/types/mapHandlers.ts index d5926c2c..63dce60f 100644 --- a/assets/js/hooks/Mapper/types/mapHandlers.ts +++ b/assets/js/hooks/Mapper/types/mapHandlers.ts @@ -24,6 +24,7 @@ export enum Commands { centerSystem = 'center_system', selectSystem = 'select_system', linkSignatureToSystem = 'link_signature_to_system', + signaturesUpdated = 'signatures_updated', } export type Command = @@ -44,7 +45,8 @@ export type Command = | Commands.routes | Commands.selectSystem | Commands.centerSystem - | Commands.linkSignatureToSystem; + | Commands.linkSignatureToSystem + | Commands.signaturesUpdated; export type CommandInit = { systems: SolarSystemRawType[]; @@ -81,6 +83,7 @@ export type CommandLinkSignatureToSystem = { solar_system_source: number; solar_system_target: number; }; +export type CommandLinkSignaturesUpdated = number; export interface CommandData { [Commands.init]: CommandInit; @@ -101,6 +104,7 @@ export interface CommandData { [Commands.selectSystem]: CommandSelectSystem; [Commands.centerSystem]: CommandCenterSystem; [Commands.linkSignatureToSystem]: CommandLinkSignatureToSystem; + [Commands.signaturesUpdated]: CommandLinkSignaturesUpdated; } export interface MapHandlers { @@ -118,6 +122,7 @@ export enum OutCommand { updateConnectionMassStatus = 'update_connection_mass_status', updateConnectionShipSizeType = 'update_connection_ship_size_type', updateConnectionLocked = 'update_connection_locked', + updateConnectionCustomInfo = 'update_connection_custom_info', updateSignatures = 'update_signatures', updateSystemName = 'update_system_name', updateSystemDescription = 'update_system_description', @@ -143,6 +148,7 @@ export enum OutCommand { getUserSettings = 'get_user_settings', updateUserSettings = 'update_user_settings', + unlinkSignature = 'unlink_signature', } export type OutCommandHandler = (event: { type: OutCommand; data: any }) => Promise; diff --git a/assets/js/hooks/Mapper/types/mapUnionTypes.ts b/assets/js/hooks/Mapper/types/mapUnionTypes.ts index aa49fb41..f05a20cd 100644 --- a/assets/js/hooks/Mapper/types/mapUnionTypes.ts +++ b/assets/js/hooks/Mapper/types/mapUnionTypes.ts @@ -7,6 +7,7 @@ import { SolarSystemConnection } from '@/hooks/Mapper/types/connection.ts'; export type MapUnionTypes = { wormholesData: Record; + wormholes: WormholeDataRaw[]; effects: Record; characters: CharacterTypeRaw[]; userCharacters: string[]; diff --git a/assets/js/hooks/Mapper/types/signatures.ts b/assets/js/hooks/Mapper/types/signatures.ts index 0ea3b028..fb5e67f2 100644 --- a/assets/js/hooks/Mapper/types/signatures.ts +++ b/assets/js/hooks/Mapper/types/signatures.ts @@ -1,23 +1,13 @@ import { SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types'; -export type SystemSignature = { - eve_id: string; - kind: string; - name: string; - description?: string; - group: string; - linked_system?: SolarSystemStaticInfoRaw; - updated_at?: string; -}; - export enum SignatureGroup { + CosmicSignature = 'Cosmic Signature', + Wormhole = 'Wormhole', GasSite = 'Gas Site', RelicSite = 'Relic Site', DataSite = 'Data Site', OreSite = 'Ore Site', CombatSite = 'Combat Site', - Wormhole = 'Wormhole', - CosmicSignature = 'Cosmic Signature', } export type GroupType = { @@ -26,3 +16,14 @@ export type GroupType = { w: number; h: number; }; + +export type SystemSignature = { + eve_id: string; + kind: string; + name: string; + description?: string; + group: SignatureGroup; + type: string; + linked_system?: SolarSystemStaticInfoRaw; + updated_at?: string; +}; diff --git a/assets/js/hooks/Mapper/types/wormholes.ts b/assets/js/hooks/Mapper/types/wormholes.ts index 3bc6eadf..ec721ab4 100644 --- a/assets/js/hooks/Mapper/types/wormholes.ts +++ b/assets/js/hooks/Mapper/types/wormholes.ts @@ -1,3 +1,9 @@ +export enum Respawn { + static = 'static', + wandering = 'wandering', + reverse = 'reverse', +} + export type WormholeDataRaw = { dest: string; id: number; @@ -5,7 +11,7 @@ export type WormholeDataRaw = { mass_regen: number; max_mass_per_jump: number; name: string; - sibling_groups: any; + respawn: Respawn[]; src: string[]; static: boolean; total_mass: number; diff --git a/assets/js/hooks/Mapper/utils/abstractContextProvider.tsx b/assets/js/hooks/Mapper/utils/abstractContextProvider.tsx new file mode 100644 index 00000000..8c2790ca --- /dev/null +++ b/assets/js/hooks/Mapper/utils/abstractContextProvider.tsx @@ -0,0 +1,26 @@ +import { createContext, ReactNode, useContext, useState } from 'react'; + +type ContextType = { + value: T; + setValue: (newValue: T) => void; +}; + +export const createGenericContext = () => { + const context = createContext | undefined>(undefined); + + const Provider = ({ children, initialValue }: { children: ReactNode; initialValue: T }) => { + const [value, setValue] = useState(initialValue); + + return {children}; + }; + + const useContextValue = () => { + const contextValue = useContext(context); + if (!contextValue) { + throw new Error('useContextValue must be used within a Provider'); + } + return contextValue; + }; + + return { Provider, useContextValue }; +}; diff --git a/assets/package.json b/assets/package.json index 00ca6c12..575b0786 100644 --- a/assets/package.json +++ b/assets/package.json @@ -31,6 +31,7 @@ "react-event-hook": "^3.1.2", "react-flow-renderer": "^10.3.17", "react-grid-layout": "^1.3.4", + "react-hook-form": "^7.53.1", "react-usestateref": "^1.0.9", "reactflow": "^11.10.4", "rxjs": "^7.8.1", diff --git a/assets/static/images/amarr-180.png b/assets/static/images/amarr-180.png new file mode 100644 index 00000000..b288db2b Binary files /dev/null and b/assets/static/images/amarr-180.png differ diff --git a/assets/static/images/caldaria-180.png b/assets/static/images/caldaria-180.png new file mode 100644 index 00000000..e60c78d9 Binary files /dev/null and b/assets/static/images/caldaria-180.png differ diff --git a/assets/static/images/gallente-180.png b/assets/static/images/gallente-180.png new file mode 100644 index 00000000..49417590 Binary files /dev/null and b/assets/static/images/gallente-180.png differ diff --git a/assets/static/images/mataria-180.png b/assets/static/images/mataria-180.png new file mode 100644 index 00000000..e42d1877 Binary files /dev/null and b/assets/static/images/mataria-180.png differ diff --git a/assets/yarn.lock b/assets/yarn.lock index a3cf423f..f0c77bae 100644 --- a/assets/yarn.lock +++ b/assets/yarn.lock @@ -3230,6 +3230,11 @@ react-grid-layout@^1.3.4: react-resizable "^3.0.5" resize-observer-polyfill "^1.5.1" +react-hook-form@^7.53.1: + version "7.53.1" + resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.53.1.tgz#3f2cd1ed2b3af99416a4ac674da2d526625add67" + integrity sha512-6aiQeBda4zjcuaugWvim9WsGqisoUk+etmFEsSUMm451/Ic8L/UAb7sRtMj3V+Hdzm6mMjU1VhiSzYUZeBm0Vg== + react-is@^16.13.1: version "16.13.1" resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" diff --git a/lib/wanderer_app.ex b/lib/wanderer_app.ex index ae5fcc57..98744b68 100644 --- a/lib/wanderer_app.ex +++ b/lib/wanderer_app.ex @@ -32,17 +32,17 @@ defmodule WandererApp do end def log_exception(kind, reason, stacktrace) do - reason = Exception.normalize(kind, reason, stacktrace) + reason = Exception.normalize(kind, reason, stacktrace) - crash_reason = - case kind do - :throw -> {{:nocatch, reason}, stacktrace} - _ -> {reason, stacktrace} - end + crash_reason = + case kind do + :throw -> {{:nocatch, reason}, stacktrace} + _ -> {reason, stacktrace} + end - Logger.error( - Exception.format(kind, reason, stacktrace), - crash_reason: crash_reason - ) - end + Logger.error( + Exception.format(kind, reason, stacktrace), + crash_reason: crash_reason + ) + end end diff --git a/lib/wanderer_app/api/map_connection.ex b/lib/wanderer_app/api/map_connection.ex index a562f2a5..f7562efb 100644 --- a/lib/wanderer_app/api/map_connection.ex +++ b/lib/wanderer_app/api/map_connection.ex @@ -28,6 +28,7 @@ defmodule WandererApp.Api.MapConnection do define(:update_time_status, action: :update_time_status) define(:update_ship_size_type, action: :update_ship_size_type) define(:update_locked, action: :update_locked) + define(:update_custom_info, action: :update_custom_info) end actions do @@ -87,6 +88,10 @@ defmodule WandererApp.Api.MapConnection do update :update_locked do accept [:locked] end + + update :update_custom_info do + accept [:custom_info] + end end attributes do @@ -131,6 +136,10 @@ defmodule WandererApp.Api.MapConnection do attribute :locked, :boolean + attribute :custom_info, :string do + allow_nil? true + end + create_timestamp(:inserted_at) update_timestamp(:updated_at) end diff --git a/lib/wanderer_app/api/map_system_signature.ex b/lib/wanderer_app/api/map_system_signature.ex index 7e4d43c7..0f5d2e0d 100644 --- a/lib/wanderer_app/api/map_system_signature.ex +++ b/lib/wanderer_app/api/map_system_signature.ex @@ -15,6 +15,7 @@ defmodule WandererApp.Api.MapSystemSignature do define(:create, action: :create) define(:update, action: :update) define(:update_linked_system, action: :update_linked_system) + define(:update_type, action: :update_type) define(:by_id, get_by: [:id], @@ -32,7 +33,8 @@ defmodule WandererApp.Api.MapSystemSignature do :name, :description, :kind, - :group + :group, + :type ] defaults [:read, :destroy] @@ -51,7 +53,8 @@ defmodule WandererApp.Api.MapSystemSignature do :name, :description, :kind, - :group + :group, + :type ] argument :system_id, :uuid, allow_nil?: false @@ -67,7 +70,8 @@ defmodule WandererApp.Api.MapSystemSignature do :name, :description, :kind, - :group + :group, + :type ] primary? true @@ -78,6 +82,10 @@ defmodule WandererApp.Api.MapSystemSignature do accept [:linked_system_id] end + update :update_type do + accept [:type] + end + read :by_system_id do argument(:system_id, :string, allow_nil?: false) @@ -104,6 +112,10 @@ defmodule WandererApp.Api.MapSystemSignature do allow_nil? true end + attribute :type, :string do + allow_nil? true + end + attribute :linked_system_id, :integer do allow_nil? true end diff --git a/lib/wanderer_app/api/user_activity.ex b/lib/wanderer_app/api/user_activity.ex index 36b15b92..2ae2fd0b 100644 --- a/lib/wanderer_app/api/user_activity.ex +++ b/lib/wanderer_app/api/user_activity.ex @@ -8,6 +8,10 @@ defmodule WandererApp.Api.UserActivity do postgres do repo(WandererApp.Repo) table("user_activity_v1") + + custom_indexes do + index [:entity_id, :event_type, :inserted_at], unique: true + end end code_interface do @@ -104,6 +108,8 @@ defmodule WandererApp.Api.UserActivity do update_timestamp(:updated_at) end + + relationships do belongs_to :character, WandererApp.Api.Character do allow_nil? true diff --git a/lib/wanderer_app/eve_data_service.ex b/lib/wanderer_app/eve_data_service.ex index c82c4663..49f35b70 100644 --- a/lib/wanderer_app/eve_data_service.ex +++ b/lib/wanderer_app/eve_data_service.ex @@ -79,7 +79,8 @@ defmodule WandererApp.EveDataService do max_mass_per_jump: row["max_mass_per_jump"], static: row["static"], mass_regen: row["mass_regen"], - sibling_groups: row["sibling_groups"] + sibling_groups: row["sibling_groups"], + respawn: row["respawn"] } end) end diff --git a/lib/wanderer_app/map/map_server.ex b/lib/wanderer_app/map/map_server.ex index 79dbde41..1a6a8e49 100644 --- a/lib/wanderer_app/map/map_server.ex +++ b/lib/wanderer_app/map/map_server.ex @@ -207,6 +207,12 @@ defmodule WandererApp.Map.Server do |> map_pid! |> GenServer.cast({&Impl.update_connection_locked/2, [connection_info]}) + def update_connection_custom_info(map_id, connection_info) when is_binary(map_id), + do: + map_id + |> map_pid! + |> GenServer.cast({&Impl.update_connection_custom_info/2, [connection_info]}) + @impl true def handle_continue(:load_state, state), do: {:noreply, state |> Impl.load_state(), {:continue, :start_map}} diff --git a/lib/wanderer_app/map/map_server_impl.ex b/lib/wanderer_app/map/map_server_impl.ex index cf7cf681..6c7f0719 100644 --- a/lib/wanderer_app/map/map_server_impl.ex +++ b/lib/wanderer_app/map/map_server_impl.ex @@ -333,7 +333,7 @@ defmodule WandererApp.Map.Server.Impl do end def delete_systems( - %{map_id: map_id, rtree_name: rtree_name, map_opts: map_opts} = state, + %{map_id: map_id, rtree_name: rtree_name} = state, removed_ids, user_id, character_id @@ -352,7 +352,7 @@ defmodule WandererApp.Map.Server.Impl do removed_ids |> Enum.each(fn solar_system_id -> map_id - |> WandererApp.MapSystemRepo.remove_from_map(solar_system_id, map_opts) + |> WandererApp.MapSystemRepo.remove_from_map(solar_system_id) |> case do {:ok, _} -> :ok @@ -471,6 +471,12 @@ defmodule WandererApp.Map.Server.Impl do ), do: _update_connection(state, :update_locked, [:locked], connection_update) + def update_connection_custom_info( + state, + 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 WandererApp.Cache.put( "map_#{map_id}:importing", @@ -1082,12 +1088,13 @@ defmodule WandererApp.Map.Server.Impl do map_id, update.solar_system_id ), - {:ok, update_map} <- _get_update_map(update, attributes), - {:ok, updated_system} <- - apply(WandererApp.MapSystemRepo, update_method, [ - system, - update_map - ]) do + {:ok, update_map} <- _get_update_map(update, attributes) do + {:ok, updated_system} = + apply(WandererApp.MapSystemRepo, update_method, [ + system, + update_map + ]) + if not is_nil(callback_fn) do callback_fn.(updated_system) end @@ -1097,7 +1104,7 @@ defmodule WandererApp.Map.Server.Impl do state else error -> - @logger.error("Failed to update system: #{inspect(error, pretty: true)}") + @logger.error("Fail ed to update system: #{inspect(error, pretty: true)}") state end end @@ -1114,7 +1121,7 @@ defmodule WandererApp.Map.Server.Impl do %{ solar_system_id: solar_system_id, coordinates: coordinates - } = _system_info, + } = system_info, user_id, character_id ) do @@ -1134,17 +1141,35 @@ defmodule WandererApp.Map.Server.Impl do {:ok, system} = case WandererApp.MapSystemRepo.get_by_map_and_solar_system_id(map_id, solar_system_id) do {:ok, existing_system} when not is_nil(existing_system) -> - @ddrt.insert( - {solar_system_id, - WandererApp.Map.PositionCalculator.get_system_bounding_rect(%{ - position_x: x, - position_y: y - })}, - rtree_name - ) + use_old_coordinates = Map.get(system_info, :use_old_coordinates, false) - existing_system - |> WandererApp.MapSystemRepo.update_position(%{position_x: x, position_y: y}) + if use_old_coordinates do + @ddrt.insert( + {solar_system_id, + WandererApp.Map.PositionCalculator.get_system_bounding_rect(%{ + position_x: existing_system.position_x, + position_y: existing_system.position_y + })}, + rtree_name + ) + + {:ok, existing_system} + else + @ddrt.insert( + {solar_system_id, + WandererApp.Map.PositionCalculator.get_system_bounding_rect(%{ + position_x: x, + position_y: y + })}, + rtree_name + ) + + {:ok, + existing_system + |> WandererApp.MapSystemRepo.update_position!(%{position_x: x, position_y: y}) + |> WandererApp.MapSystemRepo.cleanup_labels(map_opts) + |> WandererApp.MapSystemRepo.cleanup_tags()} + end _ -> {:ok, solar_system_info} = @@ -1587,13 +1612,13 @@ defmodule WandererApp.Map.Server.Impl do location.solar_system_id, old_location.solar_system_id ) do - {:ok, connection} -> + {:ok, connection} when not is_nil(connection) -> :ok = WandererApp.MapConnectionRepo.destroy(map_id, connection) broadcast!(map_id, :remove_connections, [connection]) map_id |> WandererApp.Map.remove_connection(connection) - {:error, _error} -> + _error -> :ok end end diff --git a/lib/wanderer_app/repositories/map_connection_repo.ex b/lib/wanderer_app/repositories/map_connection_repo.ex index 2c5fb69d..62bacd49 100644 --- a/lib/wanderer_app/repositories/map_connection_repo.ex +++ b/lib/wanderer_app/repositories/map_connection_repo.ex @@ -29,7 +29,7 @@ defmodule WandererApp.MapConnectionRepo do def create!(connection), do: connection |> WandererApp.Api.MapConnection.create!() - def destroy(map_id, connection) do + def destroy(map_id, connection) when not is_nil(connection) do {:ok, from_connections} = get_by_locations(map_id, connection.solar_system_source, connection.solar_system_target) @@ -49,6 +49,8 @@ defmodule WandererApp.MapConnectionRepo do end end + def destroy(_map_id, _connection), do: :ok + def destroy!(connection), do: connection |> WandererApp.Api.MapConnection.destroy!() def bulk_destroy!(connections) do @@ -82,4 +84,9 @@ defmodule WandererApp.MapConnectionRepo do do: connection |> WandererApp.Api.MapConnection.update_locked(update) + + def update_custom_info(connection, update), + do: + connection + |> WandererApp.Api.MapConnection.update_custom_info(update) end diff --git a/lib/wanderer_app/repositories/map_system_repo.ex b/lib/wanderer_app/repositories/map_system_repo.ex index cd911854..c3f3bad0 100644 --- a/lib/wanderer_app/repositories/map_system_repo.ex +++ b/lib/wanderer_app/repositories/map_system_repo.ex @@ -22,15 +22,11 @@ defmodule WandererApp.MapSystemRepo do def get_visible_by_map(map_id), do: WandererApp.Api.MapSystem.read_visible_by_map(%{map_id: map_id}) - def remove_from_map(map_id, solar_system_id, opts) do + def remove_from_map(map_id, solar_system_id) do WandererApp.Api.MapSystem.read_by_map_and_solar_system!(%{ map_id: map_id, solar_system_id: solar_system_id }) - |> cleanup_labels(opts) - |> WandererApp.Api.MapSystem.update_tag!(%{ - tag: nil - }) |> WandererApp.Api.MapSystem.update_visible(%{visible: false}) rescue error -> @@ -49,6 +45,13 @@ defmodule WandererApp.MapSystemRepo do }) 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 labels |> Jason.decode!() @@ -98,4 +101,9 @@ defmodule WandererApp.MapSystemRepo do do: system |> WandererApp.Api.MapSystem.update_position(update) + + def update_position!(system, update), + do: + system + |> WandererApp.Api.MapSystem.update_position!(update) end diff --git a/lib/wanderer_app/repositories/map_user_settings_repo.ex b/lib/wanderer_app/repositories/map_user_settings_repo.ex index 310fc866..24c704f7 100644 --- a/lib/wanderer_app/repositories/map_user_settings_repo.ex +++ b/lib/wanderer_app/repositories/map_user_settings_repo.ex @@ -1,7 +1,11 @@ defmodule WandererApp.MapUserSettingsRepo do use WandererApp, :repository - @default_form_data %{"select_on_spash" => false, "link_signature_on_splash" => false, "delete_connection_with_sigs" => false} + @default_form_data %{ + "select_on_spash" => false, + "link_signature_on_splash" => false, + "delete_connection_with_sigs" => false + } def get(map_id, user_id) do map_id diff --git a/lib/wanderer_app_web/components/user_activity.ex b/lib/wanderer_app_web/components/user_activity.ex index 4f982e4a..3f77e739 100644 --- a/lib/wanderer_app_web/components/user_activity.ex +++ b/lib/wanderer_app_web/components/user_activity.ex @@ -1,62 +1,92 @@ defmodule WandererAppWeb.UserActivity do use WandererAppWeb, :live_component + use LiveViewEvents - attr(:stream, :any, required: true) - attr(:page, :integer, required: true) - attr(:end_of_stream?, :boolean, required: true) + @impl true + def mount(socket) do + {:ok, socket} + end - def list(assigns) do + @impl true + def update(assigns, + socket + ) do + {:ok, + socket + |> handle_info_or_assign(assigns)} + end + + # attr(:can_undo_types, :list, required: false) + # attr(:stream, :any, required: true) + # attr(:page, :integer, required: true) + # attr(:end_of_stream?, :boolean, required: true) + + def render(assigns) do ~H""" - 1} - class="text-1xl fixed bottom-10 right-10 bg-zinc-700 text-white rounded-lg p-1 text-center min-w-[65px] z-50 opacity-70" - > - <%= @page %> - -
    1 && "prev-page"} - phx-viewport-bottom={!@end_of_stream? && "next-page"} - phx-page-loading - class={[ - if(@end_of_stream?, do: "pb-10", else: "pb-[calc(200vh)]"), - if(@page == 1, do: "pt-10", else: "pt-[calc(200vh)]") - ]} - > -
  • - <.activity_entry activity={activity} /> -
  • -
-
- No more activity +
+ 1} + class="text-1xl fixed bottom-10 right-10 bg-zinc-700 text-white rounded-lg p-1 text-center min-w-[65px] z-50 opacity-70" + > + <%= @page %> + +
    1 && "prev-page"} + phx-viewport-bottom={!@end_of_stream? && "next-page"} + phx-page-loading + class={[ + if(@end_of_stream?, do: "pb-10", else: "pb-[calc(200vh)]"), + if(@page == 1, do: "pt-10", else: "pt-[calc(200vh)]") + ]} + > +
  • + <.activity_entry activity={activity} can_undo_types={@can_undo_types} /> +
  • +
+
+ No more activity +
""" end attr(:activity, WandererApp.Api.UserActivity, required: true) + attr(:can_undo_types, :list, required: false) defp activity_entry(%{} = assigns) do ~H""" -
-
+
+

<.local_time id={@activity.id} at={@activity.inserted_at} />

-

- <.character_item character={@activity.character} /> -

-

+ + <.character_item :if={not is_nil(@activity.character)} character={@activity.character} /> +

+ System user / Administrator +

+ +

<%= _get_event_name(@activity.event_type) %>

<.activity_event event_type={@activity.event_type} event_data={@activity.event_data} /> + +
+ +
""" end @@ -65,7 +95,7 @@ defmodule WandererAppWeb.UserActivity do def character_item(assigns) do ~H""" -
+
{@character.name} @@ -86,11 +116,20 @@ defmodule WandererAppWeb.UserActivity do
<%= _get_event_data(@event_type, Jason.decode!(@event_data) |> Map.drop(["character_id"])) %>
+
""" end + @impl true + def handle_event("undo", %{"event-data" => event_data} = _params, socket) do + # notify_to(socket.assigns.notify_to, socket.assigns.event_name, map_slug) + IO.inspect(event_data) + + {:noreply, socket} + end + defp _get_event_name(:hub_added), do: "Hub Added" defp _get_event_name(:hub_removed), do: "Hub Removed" defp _get_event_name(:map_connection_added), do: "Connection Added" diff --git a/lib/wanderer_app_web/live/maps/map_audit_live.ex b/lib/wanderer_app_web/live/maps/map_audit_live.ex index e3c208ef..abba2a3c 100755 --- a/lib/wanderer_app_web/live/maps/map_audit_live.ex +++ b/lib/wanderer_app_web/live/maps/map_audit_live.ex @@ -1,6 +1,8 @@ defmodule WandererAppWeb.MapAuditLive do use WandererAppWeb, :live_view + require Logger + alias WandererAppWeb.UserActivity def mount( @@ -37,6 +39,7 @@ defmodule WandererAppWeb.MapAuditLive do map_name: map_name, map_slug: map_slug, activity: activity, + can_undo_types: [:systems_removed], period: period || "1H", page: 1, per_page: 25, @@ -114,11 +117,38 @@ defmodule WandererAppWeb.MapAuditLive do end end + def handle_event("undo", %{"event-data" => event_data, "event-type" => "systems_removed"}, %{assigns: %{map_id: map_id, current_user: current_user}} = socket) do + {:ok, %{"solar_system_ids" => solar_system_ids}} = Jason.decode(event_data) + + solar_system_ids + |> Enum.each(fn solar_system_id -> + WandererApp.Map.Server.add_system( + map_id, + %{ + solar_system_id: solar_system_id, + coordinates: nil, + use_old_coordinates: true + }, + current_user.id, + nil + ) + end) + + + {:noreply, socket |> put_flash(:info, "Systems restored!")} + end + @impl true def handle_event("noop", _, socket) do {:noreply, socket} end + @impl true + def handle_event(event, body, socket) do + Logger.warning(fn -> "unhandled event: #{event} #{inspect(body)}" end) + {:noreply, socket} + end + defp apply_action(socket, :index, _params) do socket |> assign(:active_page, :audit) diff --git a/lib/wanderer_app_web/live/maps/map_audit_live.html.heex b/lib/wanderer_app_web/live/maps/map_audit_live.html.heex index 2e6cef6b..00cb1c8c 100644 --- a/lib/wanderer_app_web/live/maps/map_audit_live.html.heex +++ b/lib/wanderer_app_web/live/maps/map_audit_live.html.heex @@ -3,7 +3,16 @@ class="pt-20 w-full h-full col-span-2 lg:col-span-1 p-4 pl-20 pb-20 overflow-auto" >
- + <.live_component + module={UserActivity} + id="user-activity" + notify_to={self()} + can_undo_types={@can_undo_types} + stream={@streams.activity} + page={@page} + end_of_stream?={@end_of_stream?} + event_name="activity_event" + />