mirror of
https://github.com/wanderer-industries/wanderer
synced 2025-12-05 15:25:34 +00:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8ba36b8be | ||
|
|
5bf9d99b3d | ||
|
|
7cad05342a | ||
|
|
867780e525 | ||
|
|
ff4f9a79c9 | ||
|
|
6699c36fb3 | ||
|
|
abd4556994 | ||
|
|
ccf0d17371 | ||
|
|
898584bbb6 | ||
|
|
6d7a267e39 | ||
|
|
9f656ca3cb | ||
|
|
fede6451e2 | ||
|
|
9797ad380c | ||
|
|
33bc4a4d22 | ||
|
|
30fc972d78 | ||
|
|
c022b31c79 | ||
|
|
049b06bb39 | ||
|
|
e17d5213c0 | ||
|
|
dcf681941e | ||
|
|
1cd7d40405 | ||
|
|
fbd80ba2c7 | ||
|
|
88ab85bd04 | ||
|
|
78f98744fd | ||
|
|
9c9634a927 | ||
|
|
fac60f7ddd | ||
|
|
99d68dfc0e | ||
|
|
c9b366f3e2 | ||
|
|
4e732e9491 | ||
|
|
dd5b12aa38 | ||
|
|
7bd960fba9 | ||
|
|
df6b7ae635 | ||
|
|
2ba42e0c25 | ||
|
|
3ef5590e18 | ||
|
|
4b29060c96 |
54
CHANGELOG.md
54
CHANGELOG.md
@@ -2,6 +2,60 @@
|
||||
|
||||
<!-- changelog -->
|
||||
|
||||
## [v1.62.2](https://github.com/wanderer-industries/wanderer/compare/v1.62.1...v1.62.2) (2025-05-05)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed audit export API
|
||||
|
||||
## [v1.62.1](https://github.com/wanderer-industries/wanderer/compare/v1.62.0...v1.62.1) (2025-05-05)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.62.0](https://github.com/wanderer-industries/wanderer/compare/v1.61.2...v1.62.0) (2025-05-05)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Core: added user routes support
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fixed link signature modal crash afrer destination system removed
|
||||
|
||||
* Map: Change design for tags (#358)
|
||||
|
||||
* Map: Removed paywall restriction from public routes
|
||||
|
||||
* Core: Fixed issues with structures loading
|
||||
|
||||
* Map: Removed unnecessary logs
|
||||
|
||||
* Map: Add support user routes
|
||||
|
||||
* Map: Add support for User Routes on FE side.
|
||||
|
||||
* Map: Refactor Local - show ship name, change placement of ship name. Refactor On the Map - show corp and ally logo. Fixed problem with ellipsis at long character and ship names.
|
||||
|
||||
* Map: Refactored routes widget. Add loader for routes. Prepared for custom hubs
|
||||
|
||||
* Map: Refactor init and update of mapper
|
||||
|
||||
## [v1.61.2](https://github.com/wanderer-industries/wanderer/compare/v1.61.1...v1.61.2) (2025-04-29)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed main character checking & manual systems delete logic
|
||||
|
||||
## [v1.61.1](https://github.com/wanderer-industries/wanderer/compare/v1.61.0...v1.61.1) (2025-04-26)
|
||||
|
||||
|
||||
|
||||
@@ -99,6 +99,11 @@
|
||||
.p-dropdown-item {
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
|
||||
.p-dropdown-item-label {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.p-dropdown-item-group {
|
||||
@@ -180,3 +185,16 @@
|
||||
.p-datatable .p-datatable-tbody > tr.p-highlight {
|
||||
background: initial;
|
||||
}
|
||||
|
||||
.suppress-menu-behaviour {
|
||||
pointer-events: none;
|
||||
|
||||
.p-menuitem-content {
|
||||
pointer-events: initial;
|
||||
background-color: initial !important;
|
||||
}
|
||||
.p-menuitem-content:hover {
|
||||
background-color: initial !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { WaypointSetContextHandler } from '@/hooks/Mapper/components/contexts/ty
|
||||
|
||||
export interface ContextMenuSystemProps {
|
||||
hubs: string[];
|
||||
userHubs: string[];
|
||||
contextMenuRef: RefObject<ContextMenu>;
|
||||
systemId: string | undefined;
|
||||
systems: SolarSystemRawType[];
|
||||
@@ -13,6 +14,7 @@ export interface ContextMenuSystemProps {
|
||||
onLockToggle(): void;
|
||||
onOpenSettings(): void;
|
||||
onHubToggle(): void;
|
||||
onUserHubToggle(): void;
|
||||
onSystemTag(val?: string): void;
|
||||
onSystemStatus(val: number): void;
|
||||
onSystemLabels(val: string): void;
|
||||
@@ -25,7 +27,7 @@ export const ContextMenuSystem: React.FC<ContextMenuSystemProps> = ({ contextMen
|
||||
|
||||
return (
|
||||
<>
|
||||
<ContextMenu model={items} ref={contextMenuRef} breakpoint="767px" />
|
||||
<ContextMenu className="min-w-[200px]" model={items} ref={contextMenuRef} breakpoint="767px" />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1 +1 @@
|
||||
export * from './useTagMenu.ts';
|
||||
export * from './useTagMenu.tsx';
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
import { MenuItem } from 'primereact/menuitem';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { SolarSystemRawType } from '@/hooks/Mapper/types';
|
||||
import { getSystemById } from '@/hooks/Mapper/helpers';
|
||||
import clsx from 'clsx';
|
||||
import { GRADIENT_MENU_ACTIVE_CLASSES } from '@/hooks/Mapper/constants.ts';
|
||||
|
||||
const AVAILABLE_LETTERS = ['A', 'B', 'C', 'D', 'E', 'F', 'X', 'Y', 'Z'];
|
||||
const AVAILABLE_NUMBERS = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
||||
|
||||
export const useTagMenu = (
|
||||
systems: SolarSystemRawType[],
|
||||
systemId: string | undefined,
|
||||
onSystemTag: (val?: string) => void,
|
||||
): (() => MenuItem) => {
|
||||
const ref = useRef({ onSystemTag, systems, systemId });
|
||||
ref.current = { onSystemTag, systems, systemId };
|
||||
|
||||
return useCallback(() => {
|
||||
const { onSystemTag, systemId, systems } = ref.current;
|
||||
const system = systemId ? getSystemById(systems, systemId) : undefined;
|
||||
|
||||
const isSelectedLetters = AVAILABLE_LETTERS.includes(system?.tag ?? '');
|
||||
const isSelectedNumbers = AVAILABLE_NUMBERS.includes(system?.tag ?? '');
|
||||
|
||||
const menuItem: MenuItem = {
|
||||
label: 'Tag',
|
||||
icon: PrimeIcons.HASHTAG,
|
||||
className: clsx({ [GRADIENT_MENU_ACTIVE_CLASSES]: isSelectedLetters || isSelectedNumbers }),
|
||||
items: [
|
||||
...(system?.tag !== '' && system?.tag !== null
|
||||
? [
|
||||
{
|
||||
label: 'Clear',
|
||||
icon: PrimeIcons.BAN,
|
||||
command: () => onSystemTag(),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
label: 'Letter',
|
||||
icon: PrimeIcons.TAGS,
|
||||
className: clsx({ [GRADIENT_MENU_ACTIVE_CLASSES]: isSelectedLetters }),
|
||||
items: AVAILABLE_LETTERS.map(x => ({
|
||||
label: x,
|
||||
icon: PrimeIcons.TAG,
|
||||
command: () => onSystemTag(x),
|
||||
className: clsx({ [GRADIENT_MENU_ACTIVE_CLASSES]: system?.tag === x }),
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: 'Digit',
|
||||
icon: PrimeIcons.TAGS,
|
||||
className: clsx({ [GRADIENT_MENU_ACTIVE_CLASSES]: isSelectedNumbers }),
|
||||
items: AVAILABLE_NUMBERS.map(x => ({
|
||||
label: x,
|
||||
icon: PrimeIcons.TAG,
|
||||
command: () => onSystemTag(x),
|
||||
className: clsx({ [GRADIENT_MENU_ACTIVE_CLASSES]: system?.tag === x }),
|
||||
})),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return menuItem;
|
||||
}, []);
|
||||
};
|
||||
@@ -0,0 +1,95 @@
|
||||
import { MenuItem } from 'primereact/menuitem';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { SolarSystemRawType } from '@/hooks/Mapper/types';
|
||||
import { getSystemById } from '@/hooks/Mapper/helpers';
|
||||
import clsx from 'clsx';
|
||||
import { GRADIENT_MENU_ACTIVE_CLASSES } from '@/hooks/Mapper/constants.ts';
|
||||
import { LayoutEventBlocker } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { Button } from 'primereact/button';
|
||||
|
||||
const AVAILABLE_TAGS = [
|
||||
'A',
|
||||
'B',
|
||||
'C',
|
||||
'D',
|
||||
'E',
|
||||
'F',
|
||||
'G',
|
||||
'H',
|
||||
'I',
|
||||
'X',
|
||||
'Y',
|
||||
'Z',
|
||||
'0',
|
||||
'1',
|
||||
'2',
|
||||
'3',
|
||||
'4',
|
||||
'5',
|
||||
'6',
|
||||
'7',
|
||||
'8',
|
||||
'9',
|
||||
];
|
||||
|
||||
export const useTagMenu = (
|
||||
systems: SolarSystemRawType[],
|
||||
systemId: string | undefined,
|
||||
onSystemTag: (val?: string) => void,
|
||||
): (() => MenuItem) => {
|
||||
const ref = useRef({ onSystemTag, systems, systemId });
|
||||
ref.current = { onSystemTag, systems, systemId };
|
||||
|
||||
return useCallback(() => {
|
||||
const { onSystemTag, systemId, systems } = ref.current;
|
||||
const system = systemId ? getSystemById(systems, systemId) : undefined;
|
||||
|
||||
const isSelectedTag = AVAILABLE_TAGS.includes(system?.tag ?? '');
|
||||
|
||||
const menuItem: MenuItem = {
|
||||
label: 'Tag',
|
||||
icon: PrimeIcons.HASHTAG,
|
||||
className: clsx({ [GRADIENT_MENU_ACTIVE_CLASSES]: isSelectedTag }),
|
||||
items: [
|
||||
{
|
||||
label: 'Digit',
|
||||
icon: PrimeIcons.TAGS,
|
||||
className: '!h-[128px] suppress-menu-behaviour',
|
||||
template: () => {
|
||||
return (
|
||||
<LayoutEventBlocker className="flex flex-col gap-1 w-[200px] h-full px-2">
|
||||
<div className="grid grid-cols-[auto_auto_auto_auto_auto_auto] gap-1">
|
||||
{AVAILABLE_TAGS.map(x => (
|
||||
<Button
|
||||
outlined={system?.tag !== x}
|
||||
severity="warning"
|
||||
key={x}
|
||||
value={x}
|
||||
size="small"
|
||||
className="p-[3px] justify-center"
|
||||
onClick={() => system?.tag !== x && onSystemTag(x)}
|
||||
>
|
||||
{x}
|
||||
</Button>
|
||||
))}
|
||||
<Button
|
||||
disabled={!isSelectedTag}
|
||||
icon="pi pi-ban"
|
||||
size="small"
|
||||
className="!p-0 !w-[initial] justify-center"
|
||||
outlined
|
||||
severity="help"
|
||||
onClick={() => onSystemTag()}
|
||||
></Button>
|
||||
</div>
|
||||
</LayoutEventBlocker>
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return menuItem;
|
||||
}, []);
|
||||
};
|
||||
@@ -8,19 +8,25 @@ import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
|
||||
|
||||
interface UseContextMenuSystemHandlersProps {
|
||||
hubs: string[];
|
||||
userHubs: string[];
|
||||
systems: SolarSystemRawType[];
|
||||
outCommand: OutCommandHandler;
|
||||
}
|
||||
|
||||
export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseContextMenuSystemHandlersProps) => {
|
||||
export const useContextMenuSystemHandlers = ({
|
||||
systems,
|
||||
hubs,
|
||||
userHubs,
|
||||
outCommand,
|
||||
}: UseContextMenuSystemHandlersProps) => {
|
||||
const contextMenuRef = useRef<ContextMenu | null>(null);
|
||||
|
||||
const [system, setSystem] = useState<string>();
|
||||
|
||||
const { deleteSystems } = useDeleteSystems();
|
||||
|
||||
const ref = useRef({ hubs, system, systems, outCommand, deleteSystems });
|
||||
ref.current = { hubs, system, systems, outCommand, deleteSystems };
|
||||
const ref = useRef({ hubs, userHubs, system, systems, outCommand, deleteSystems });
|
||||
ref.current = { hubs, userHubs, system, systems, outCommand, deleteSystems };
|
||||
|
||||
const open = useCallback((ev: any, systemId: string) => {
|
||||
setSystem(systemId);
|
||||
@@ -72,6 +78,21 @@ export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseC
|
||||
setSystem(undefined);
|
||||
}, []);
|
||||
|
||||
const onUserHubToggle = useCallback(() => {
|
||||
const { userHubs, system, outCommand } = ref.current;
|
||||
if (!system) {
|
||||
return;
|
||||
}
|
||||
|
||||
outCommand({
|
||||
type: !userHubs.includes(system) ? OutCommand.addUserHub : OutCommand.deleteUserHub,
|
||||
data: {
|
||||
system_id: system,
|
||||
},
|
||||
});
|
||||
setSystem(undefined);
|
||||
}, []);
|
||||
|
||||
const onSystemTag = useCallback((tag?: string) => {
|
||||
const { system, outCommand } = ref.current;
|
||||
if (!system) {
|
||||
@@ -104,7 +125,6 @@ export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseC
|
||||
setSystem(undefined);
|
||||
}, []);
|
||||
|
||||
|
||||
const onSystemStatus = useCallback((status: number) => {
|
||||
const { system, outCommand } = ref.current;
|
||||
if (!system) {
|
||||
@@ -177,6 +197,7 @@ export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseC
|
||||
onDeleteSystem,
|
||||
onLockToggle,
|
||||
onHubToggle,
|
||||
onUserHubToggle,
|
||||
onSystemTag,
|
||||
onSystemTemporaryName,
|
||||
onSystemStatus,
|
||||
|
||||
@@ -10,11 +10,13 @@ import { useMapCheckPermissions } from '@/hooks/Mapper/mapRootProvider/hooks/api
|
||||
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
|
||||
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace.ts';
|
||||
import { getSystemStaticInfo } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic';
|
||||
import { MapAddIcon, MapDeleteIcon, MapUserAddIcon, MapUserDeleteIcon } from '@/hooks/Mapper/icons';
|
||||
|
||||
export const useContextMenuSystemItems = ({
|
||||
onDeleteSystem,
|
||||
onLockToggle,
|
||||
onHubToggle,
|
||||
onUserHubToggle,
|
||||
onSystemTag,
|
||||
onSystemStatus,
|
||||
onSystemLabels,
|
||||
@@ -23,6 +25,7 @@ export const useContextMenuSystemItems = ({
|
||||
onWaypointSet,
|
||||
systemId,
|
||||
hubs,
|
||||
userHubs,
|
||||
systems,
|
||||
}: Omit<ContextMenuSystemProps, 'contextMenuRef'>) => {
|
||||
const getTags = useTagMenu(systems, systemId, onSystemTag);
|
||||
@@ -61,10 +64,23 @@ export const useContextMenuSystemItems = ({
|
||||
...getLabels(),
|
||||
...getWaypointMenu(systemId, systemStaticInfo.system_class),
|
||||
{
|
||||
label: !hubs.includes(systemId) ? 'Add in Routes' : 'Remove from Routes',
|
||||
icon: PrimeIcons.MAP_MARKER,
|
||||
label: !hubs.includes(systemId) ? 'Add Route' : 'Remove Route',
|
||||
icon: !hubs.includes(systemId) ? (
|
||||
<MapAddIcon className="mr-1 relative left-[-2px]" />
|
||||
) : (
|
||||
<MapDeleteIcon className="mr-1 relative left-[-2px]" />
|
||||
),
|
||||
command: onHubToggle,
|
||||
},
|
||||
{
|
||||
label: !userHubs.includes(systemId) ? 'Add User Route' : 'Remove User Route',
|
||||
icon: !userHubs.includes(systemId) ? (
|
||||
<MapUserAddIcon className="mr-1 relative left-[-2px]" />
|
||||
) : (
|
||||
<MapUserDeleteIcon className="mr-1 relative left-[-2px]" />
|
||||
),
|
||||
command: onUserHubToggle,
|
||||
},
|
||||
...(system.locked
|
||||
? canLockSystem
|
||||
? [
|
||||
|
||||
@@ -11,6 +11,7 @@ import { FastSystemActions } from '@/hooks/Mapper/components/contexts/components
|
||||
import { useJumpPlannerMenu } from '@/hooks/Mapper/components/contexts/hooks';
|
||||
import { Route } from '@/hooks/Mapper/types/routes.ts';
|
||||
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace.ts';
|
||||
import { MapAddIcon, MapDeleteIcon } from '@/hooks/Mapper/icons';
|
||||
|
||||
export interface ContextMenuSystemInfoProps {
|
||||
systemStatics: Map<number, SolarSystemStaticInfoRaw>;
|
||||
@@ -69,8 +70,12 @@ export const ContextMenuSystemInfo: React.FC<ContextMenuSystemInfoProps> = ({
|
||||
...getJumpPlannerMenu(system, routes),
|
||||
...getWaypointMenu(systemId, system.system_class),
|
||||
{
|
||||
label: !hubs.includes(systemId) ? 'Add in Routes' : 'Remove from Routes',
|
||||
icon: PrimeIcons.MAP_MARKER,
|
||||
label: !hubs.includes(systemId) ? 'Add Route' : 'Remove Route',
|
||||
icon: !hubs.includes(systemId) ? (
|
||||
<MapAddIcon className="mr-1 relative left-[-2px]" />
|
||||
) : (
|
||||
<MapDeleteIcon className="mr-1 relative left-[-2px]" />
|
||||
),
|
||||
command: onHubToggle,
|
||||
},
|
||||
...(!systemOnMap
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import * as React from 'react';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { ContextMenu } from 'primereact/contextmenu';
|
||||
import { Commands, OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { Commands, OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { WaypointSetContextHandler } from '@/hooks/Mapper/components/contexts/types.ts';
|
||||
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
||||
import { SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types';
|
||||
import { emitMapEvent } from '@/hooks/Mapper/events';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useRouteProvider } from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/RoutesProvider.tsx';
|
||||
|
||||
interface UseContextMenuSystemHandlersProps {
|
||||
hubs: string[];
|
||||
outCommand: OutCommandHandler;
|
||||
}
|
||||
export const useContextMenuSystemInfoHandlers = () => {
|
||||
const { outCommand } = useMapRootState();
|
||||
const { hubs = [], toggleHubCommand } = useRouteProvider();
|
||||
|
||||
export const useContextMenuSystemInfoHandlers = ({ hubs, outCommand }: UseContextMenuSystemHandlersProps) => {
|
||||
const contextMenuRef = useRef<ContextMenu | null>(null);
|
||||
|
||||
const [system, setSystem] = useState<string>();
|
||||
const routeRef = useRef<(SolarSystemStaticInfoRaw | undefined)[]>([]);
|
||||
|
||||
const ref = useRef({ hubs, system, outCommand });
|
||||
ref.current = { hubs, system, outCommand };
|
||||
const ref = useRef({ hubs, system, outCommand, toggleHubCommand });
|
||||
ref.current = { hubs, system, outCommand, toggleHubCommand };
|
||||
|
||||
const open = useCallback(
|
||||
(ev: React.SyntheticEvent, systemId: string, route: (SolarSystemStaticInfoRaw | undefined)[]) => {
|
||||
@@ -33,17 +33,12 @@ export const useContextMenuSystemInfoHandlers = ({ hubs, outCommand }: UseContex
|
||||
);
|
||||
|
||||
const onHubToggle = useCallback(() => {
|
||||
const { hubs, system, outCommand } = ref.current;
|
||||
const { system } = ref.current;
|
||||
if (!system) {
|
||||
return;
|
||||
}
|
||||
|
||||
outCommand({
|
||||
type: !hubs.includes(system) ? OutCommand.addHub : OutCommand.deleteHub,
|
||||
data: {
|
||||
system_id: system,
|
||||
},
|
||||
});
|
||||
ref.current.toggleHubCommand(system);
|
||||
setSystem(undefined);
|
||||
}, []);
|
||||
|
||||
@@ -59,6 +54,8 @@ export const useContextMenuSystemInfoHandlers = ({ hubs, outCommand }: UseContex
|
||||
system_id: solarSystemId,
|
||||
},
|
||||
});
|
||||
|
||||
// TODO add it to some queue
|
||||
setTimeout(() => {
|
||||
emitMapEvent({
|
||||
name: Commands.centerSystem,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { LayoutEventBlocker, WdImageSize, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { ANOIK_ICON, DOTLAN_ICON, ZKB_ICON } from '@/hooks/Mapper/icons.ts';
|
||||
import { ANOIK_ICON, DOTLAN_ICON, ZKB_ICON } from '@/hooks/Mapper/icons';
|
||||
|
||||
import classes from './FastSystemActions.module.scss';
|
||||
import clsx from 'clsx';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getSystemById } from '@/hooks/Mapper/helpers';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useMemo } from 'react';
|
||||
import { getSystemById } from '@/hooks/Mapper/helpers';
|
||||
import { getSystemStaticInfo } from '../../mapRootProvider/hooks/useLoadSystemStatic';
|
||||
|
||||
interface UseSystemInfoProps {
|
||||
@@ -17,7 +17,7 @@ export const useSystemInfo = ({ systemId }: UseSystemInfoProps) => {
|
||||
const dynamicInfo = getSystemById(systems, systemId);
|
||||
|
||||
if (!staticInfo || !dynamicInfo) {
|
||||
throw new Error(`Error on getting system ${systemId}`);
|
||||
return { dynamicInfo, staticInfo, leadsTo: [] };
|
||||
}
|
||||
|
||||
const leadsTo = connections
|
||||
|
||||
@@ -38,6 +38,8 @@ const INITIAL_DATA: MapData = {
|
||||
systemSignatures: {} as Record<string, SystemSignature[]>,
|
||||
options: {} as Record<string, string | boolean>,
|
||||
isSubscriptionActive: false,
|
||||
mainCharacterEveId: null,
|
||||
followingCharacterEveId: null,
|
||||
};
|
||||
|
||||
export interface MapContextProps {
|
||||
|
||||
@@ -26,11 +26,7 @@ export const KillsCounter = ({
|
||||
children,
|
||||
size = TooltipSize.xs,
|
||||
}: KillsBookmarkTooltipProps) => {
|
||||
const {
|
||||
isLoading,
|
||||
kills: detailedKills,
|
||||
systemNameMap,
|
||||
} = useKillsCounter({
|
||||
const { isLoading, kills: detailedKills } = useKillsCounter({
|
||||
realSystemId: systemId,
|
||||
});
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ import { CharItemProps, LocalCharactersList } from '../../../mapInterface/widget
|
||||
import { useLocalCharactersItemTemplate } from '../../../mapInterface/widgets/LocalCharacters/hooks/useLocalCharacters';
|
||||
import { useLocalCharacterWidgetSettings } from '../../../mapInterface/widgets/LocalCharacters/hooks/useLocalWidgetSettings';
|
||||
import classes from './SolarSystemLocalCounter.module.scss';
|
||||
import { AvailableThemes } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useTheme } from '@/hooks/Mapper/hooks/useTheme.ts';
|
||||
import { AvailableThemes } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
|
||||
interface LocalCounterProps {
|
||||
localCounterCharacters: Array<CharItemProps>;
|
||||
|
||||
@@ -16,22 +16,20 @@ import { LocalCounter } from './SolarSystemLocalCounter';
|
||||
import { KillsCounter } from './SolarSystemKillsCounter';
|
||||
import { TooltipSize } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper/utils.ts';
|
||||
import { TooltipPosition, WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { Tag } from 'primereact/tag';
|
||||
|
||||
// let render = 0;
|
||||
export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>) => {
|
||||
const nodeVars = useSolarSystemNode(props);
|
||||
const { localCounterCharacters } = useLocalCounter(nodeVars);
|
||||
const localKillsCount = useNodeKillsCount(nodeVars.solarSystemId, nodeVars.killsCount);
|
||||
|
||||
// console.log('JOipP', `render ${nodeVars.id}`, render++);
|
||||
|
||||
return (
|
||||
<>
|
||||
{nodeVars.visible && (
|
||||
<div className={classes.Bookmarks}>
|
||||
{nodeVars.labelCustom !== '' && (
|
||||
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.custom)}>
|
||||
<span className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]">{nodeVars.labelCustom}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{nodeVars.isShattered && (
|
||||
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.shattered, '!pr-[2px]')}>
|
||||
<WdTooltipWrapper content="Shattered" position={TooltipPosition.top}>
|
||||
@@ -55,6 +53,12 @@ export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>
|
||||
</KillsCounter>
|
||||
)}
|
||||
|
||||
{nodeVars.labelCustom !== '' && (
|
||||
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.custom)}>
|
||||
<span className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]">{nodeVars.labelCustom}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{nodeVars.labelsInfo.map(x => (
|
||||
<div key={x.id} className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[x.id])}>
|
||||
{x.shortName}
|
||||
@@ -86,7 +90,11 @@ export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>
|
||||
</div>
|
||||
|
||||
{nodeVars.tag != null && nodeVars.tag !== '' && (
|
||||
<div className={clsx(classes.TagTitle, 'text-sky-400 font-medium')}>{nodeVars.tag}</div>
|
||||
<Tag
|
||||
value={nodeVars.tag}
|
||||
severity="warning"
|
||||
className="py-0 px-[2px] text-[9px] [&_.p-tag-value]:leading-[1.3]"
|
||||
></Tag>
|
||||
)}
|
||||
|
||||
<div
|
||||
|
||||
@@ -17,21 +17,18 @@ import { KillsCounter } from './SolarSystemKillsCounter';
|
||||
import { TooltipPosition, WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { TooltipSize } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper/utils.ts';
|
||||
|
||||
// let render = 0;
|
||||
export const SolarSystemNodeTheme = memo((props: NodeProps<MapSolarSystemType>) => {
|
||||
const nodeVars = useSolarSystemNode(props);
|
||||
const { localCounterCharacters } = useLocalCounter(nodeVars);
|
||||
const localKillsCount = useNodeKillsCount(nodeVars.solarSystemId, nodeVars.killsCount);
|
||||
|
||||
// console.log('JOipP', `render ${nodeVars.id}`, render++);
|
||||
|
||||
return (
|
||||
<>
|
||||
{nodeVars.visible && (
|
||||
<div className={classes.Bookmarks}>
|
||||
{nodeVars.labelCustom !== '' && (
|
||||
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.custom)}>
|
||||
<span className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]">{nodeVars.labelCustom}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{nodeVars.isShattered && (
|
||||
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.shattered, '!pr-[2px]')}>
|
||||
<WdTooltipWrapper content="Shattered" position={TooltipPosition.top}>
|
||||
@@ -55,6 +52,12 @@ export const SolarSystemNodeTheme = memo((props: NodeProps<MapSolarSystemType>)
|
||||
</KillsCounter>
|
||||
)}
|
||||
|
||||
{nodeVars.labelCustom !== '' && (
|
||||
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.custom)}>
|
||||
<span className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]">{nodeVars.labelCustom}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{nodeVars.labelsInfo.map(x => (
|
||||
<div key={x.id} className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[x.id])}>
|
||||
{x.shortName}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MapData, useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { CommandKillsUpdated, CommandMapUpdated } from '@/hooks/Mapper/types';
|
||||
import { useCallback, useRef } from 'react';
|
||||
|
||||
export const useMapCommands = () => {
|
||||
const { update } = useMapState();
|
||||
@@ -8,13 +8,21 @@ export const useMapCommands = () => {
|
||||
const ref = useRef({ update });
|
||||
ref.current = { update };
|
||||
|
||||
const mapUpdated = useCallback(({ hubs }: CommandMapUpdated) => {
|
||||
const mapUpdated = useCallback(({ hubs, system_signatures, kills }: CommandMapUpdated) => {
|
||||
const out: Partial<MapData> = {};
|
||||
|
||||
if (hubs) {
|
||||
out.hubs = hubs;
|
||||
}
|
||||
|
||||
if (system_signatures) {
|
||||
out.systemSignatures = system_signatures;
|
||||
}
|
||||
|
||||
if (kills) {
|
||||
out.kills = kills.reduce((acc, x) => ({ ...acc, [x.solar_system_id]: x.kills }), {});
|
||||
}
|
||||
|
||||
ref.current.update(out);
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -13,28 +13,26 @@ interface MapEvent {
|
||||
payload?: Kill[];
|
||||
}
|
||||
|
||||
export function useNodeKillsCount(
|
||||
systemId: number | string,
|
||||
initialKillsCount: number | null
|
||||
): number | null {
|
||||
export function useNodeKillsCount(systemId: number | string, initialKillsCount: number | null): number | null {
|
||||
const [killsCount, setKillsCount] = useState<number | null>(initialKillsCount);
|
||||
|
||||
useEffect(() => {
|
||||
setKillsCount(initialKillsCount);
|
||||
}, [initialKillsCount]);
|
||||
|
||||
const handleEvent = useCallback((event: MapEvent): boolean => {
|
||||
if (event.name === Commands.killsUpdated && Array.isArray(event.payload)) {
|
||||
const killForSystem = event.payload.find(
|
||||
kill => kill.solar_system_id.toString() === systemId.toString()
|
||||
);
|
||||
if (killForSystem && typeof killForSystem.kills === 'number') {
|
||||
setKillsCount(killForSystem.kills);
|
||||
const handleEvent = useCallback(
|
||||
(event: MapEvent): boolean => {
|
||||
if (event.name === Commands.killsUpdated && Array.isArray(event.payload)) {
|
||||
const killForSystem = event.payload.find(kill => kill.solar_system_id.toString() === systemId.toString());
|
||||
if (killForSystem && typeof killForSystem.kills === 'number') {
|
||||
setKillsCount(killForSystem.kills);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, [systemId]);
|
||||
return false;
|
||||
},
|
||||
[systemId],
|
||||
);
|
||||
|
||||
useMapEventListener(handleEvent);
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarS
|
||||
} = data;
|
||||
|
||||
const {
|
||||
interfaceSettings,
|
||||
storedSettings: { interfaceSettings },
|
||||
data: { systemSignatures: mapSystemSignatures },
|
||||
} = useMapRootState();
|
||||
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import { useCallback, useMemo, useRef } from 'react';
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
|
||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { CommandLinkSignatureToSystem, SignatureGroup, SystemSignature, TimeStatus } from '@/hooks/Mapper/types';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { SystemSignaturesContent } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignaturesContent';
|
||||
import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo';
|
||||
import { getWhSize } from '@/hooks/Mapper/helpers/getWhSize';
|
||||
import { useSystemInfo } from '@/hooks/Mapper/components/hooks';
|
||||
import {
|
||||
SOLAR_SYSTEM_CLASS_IDS,
|
||||
SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS,
|
||||
WORMHOLES_ADDITIONAL_INFO_BY_SHORT_NAME,
|
||||
} from '@/hooks/Mapper/components/map/constants.ts';
|
||||
import { K162_TYPES_MAP } from '@/hooks/Mapper/constants.ts';
|
||||
import {
|
||||
SETTINGS_KEYS,
|
||||
SignatureSettingsType,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||
import { SystemSignaturesContent } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignaturesContent';
|
||||
import { K162_TYPES_MAP } from '@/hooks/Mapper/constants.ts';
|
||||
import { getWhSize } from '@/hooks/Mapper/helpers/getWhSize';
|
||||
import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { CommandLinkSignatureToSystem, SignatureGroup, SystemSignature, TimeStatus } from '@/hooks/Mapper/types';
|
||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
|
||||
const K162_SIGNATURE_TYPE = WORMHOLES_ADDITIONAL_INFO_BY_SHORT_NAME['K162'].shortName;
|
||||
|
||||
@@ -49,7 +49,9 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat
|
||||
ref.current = { outCommand };
|
||||
|
||||
// Get system info for the target system
|
||||
const { staticInfo: targetSystemInfo } = useSystemInfo({ systemId: `${data.solar_system_target}` });
|
||||
const { staticInfo: targetSystemInfo, dynamicInfo: targetSystemDynamicInfo } = useSystemInfo({
|
||||
systemId: `${data.solar_system_target}`,
|
||||
});
|
||||
|
||||
// Get the system class group for the target system
|
||||
const targetSystemClassGroup = useMemo(() => {
|
||||
@@ -160,6 +162,12 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat
|
||||
[data, setVisible, wormholes],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!targetSystemDynamicInfo) {
|
||||
handleHide();
|
||||
}
|
||||
}, [targetSystemDynamicInfo]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
header="Select signature to link"
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { WindowProps } from '@/hooks/Mapper/components/ui-kit/WindowManager/types.ts';
|
||||
import {
|
||||
CommentsWidget,
|
||||
LocalCharacters,
|
||||
RoutesWidget,
|
||||
SystemInfo,
|
||||
SystemSignatures,
|
||||
SystemStructures,
|
||||
WRoutesPublic,
|
||||
WRoutesUser,
|
||||
WSystemKills,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets';
|
||||
import { CommentsWidget } from '@/hooks/Mapper/components/mapInterface/widgets/CommentsWidget';
|
||||
|
||||
export const CURRENT_WINDOWS_VERSION = 9;
|
||||
export const WINDOWS_LOCAL_STORE_KEY = 'windows:settings:v2';
|
||||
@@ -20,6 +21,7 @@ export enum WidgetsIds {
|
||||
structures = 'structures',
|
||||
kills = 'kills',
|
||||
comments = 'comments',
|
||||
userRoutes = 'userRoutes',
|
||||
}
|
||||
|
||||
export const STORED_VISIBLE_WIDGETS_DEFAULT = [
|
||||
@@ -56,7 +58,14 @@ export const DEFAULT_WIDGETS: WindowProps[] = [
|
||||
position: { x: 10, y: 530 },
|
||||
size: { width: 510, height: 200 },
|
||||
zIndex: 0,
|
||||
content: () => <RoutesWidget />,
|
||||
content: () => <WRoutesPublic />,
|
||||
},
|
||||
{
|
||||
id: WidgetsIds.userRoutes,
|
||||
position: { x: 10, y: 530 },
|
||||
size: { width: 510, height: 200 },
|
||||
zIndex: 0,
|
||||
content: () => <WRoutesUser />,
|
||||
},
|
||||
{
|
||||
id: WidgetsIds.structures,
|
||||
@@ -103,6 +112,10 @@ export const WIDGETS_CHECKBOXES_PROPS: WidgetsCheckboxesType = [
|
||||
id: WidgetsIds.routes,
|
||||
label: 'Routes',
|
||||
},
|
||||
{
|
||||
id: WidgetsIds.userRoutes,
|
||||
label: 'User Routes',
|
||||
},
|
||||
{
|
||||
id: WidgetsIds.structures,
|
||||
label: 'Structures',
|
||||
|
||||
@@ -22,7 +22,7 @@ export const LocalCharactersItemTemplate = ({ showShipName, ...options }: LocalC
|
||||
)}
|
||||
style={{ height: `${options.props.itemSize}px` }}
|
||||
>
|
||||
<CharacterCard showShipName={showShipName} {...options} />
|
||||
<CharacterCard showShipName={showShipName} showTicker {...options} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,79 +1,34 @@
|
||||
import React, { createContext, useContext, useEffect } from 'react';
|
||||
import { ContextStoreDataUpdate, useContextStore } from '@/hooks/Mapper/utils';
|
||||
import { SESSION_KEY } from '@/hooks/Mapper/constants.ts';
|
||||
import React, { createContext, forwardRef, useContext, useImperativeHandle, useState } from 'react';
|
||||
import {
|
||||
RoutesImperativeHandle,
|
||||
RoutesProviderInnerProps,
|
||||
RoutesWidgetProps,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/types.ts';
|
||||
|
||||
export type RoutesType = {
|
||||
path_type: 'shortest' | 'secure' | 'insecure';
|
||||
include_mass_crit: boolean;
|
||||
include_eol: boolean;
|
||||
include_frig: boolean;
|
||||
include_cruise: boolean;
|
||||
include_thera: boolean;
|
||||
avoid_wormholes: boolean;
|
||||
avoid_pochven: boolean;
|
||||
avoid_edencom: boolean;
|
||||
avoid_triglavian: boolean;
|
||||
avoid: number[];
|
||||
};
|
||||
|
||||
interface MapProviderProps {
|
||||
type MapProviderProps = {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
} & RoutesWidgetProps;
|
||||
|
||||
export const DEFAULT_SETTINGS: RoutesType = {
|
||||
path_type: 'shortest',
|
||||
include_mass_crit: true,
|
||||
include_eol: true,
|
||||
include_frig: true,
|
||||
include_cruise: true,
|
||||
include_thera: true,
|
||||
avoid_wormholes: false,
|
||||
avoid_pochven: false,
|
||||
avoid_edencom: false,
|
||||
avoid_triglavian: false,
|
||||
avoid: [],
|
||||
};
|
||||
|
||||
export interface MapContextProps {
|
||||
update: ContextStoreDataUpdate<RoutesType>;
|
||||
data: RoutesType;
|
||||
}
|
||||
|
||||
const RoutesContext = createContext<MapContextProps>({
|
||||
const RoutesContext = createContext<RoutesProviderInnerProps>({
|
||||
update: () => {},
|
||||
data: { ...DEFAULT_SETTINGS },
|
||||
// @ts-ignore
|
||||
data: {},
|
||||
});
|
||||
|
||||
export const RoutesProvider: React.FC<MapProviderProps> = ({ children }) => {
|
||||
const { update, ref } = useContextStore<RoutesType>(
|
||||
{ ...DEFAULT_SETTINGS },
|
||||
{
|
||||
onAfterAUpdate: values => {
|
||||
localStorage.setItem(SESSION_KEY.routes, JSON.stringify(values));
|
||||
},
|
||||
export const RoutesProvider = forwardRef<RoutesImperativeHandle, MapProviderProps>(({ children, ...props }, ref) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
stopLoading() {
|
||||
setLoading(false);
|
||||
},
|
||||
);
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
const items = localStorage.getItem(SESSION_KEY.routes);
|
||||
if (items) {
|
||||
update(JSON.parse(items));
|
||||
}
|
||||
}, [update]);
|
||||
|
||||
return (
|
||||
<RoutesContext.Provider
|
||||
value={{
|
||||
update,
|
||||
data: ref,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</RoutesContext.Provider>
|
||||
);
|
||||
};
|
||||
return <RoutesContext.Provider value={{ ...props, loading, setLoading }}>{children}</RoutesContext.Provider>;
|
||||
});
|
||||
RoutesProvider.displayName = 'RoutesProvider';
|
||||
|
||||
export const useRouteProvider = () => {
|
||||
const context = useContext<MapContextProps>(RoutesContext);
|
||||
const context = useContext<RoutesProviderInnerProps>(RoutesContext);
|
||||
return context;
|
||||
};
|
||||
|
||||
@@ -2,13 +2,14 @@ import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import {
|
||||
LayoutEventBlocker,
|
||||
LoadingWrapper,
|
||||
SystemViewStandalone,
|
||||
TooltipPosition,
|
||||
WdCheckbox,
|
||||
WdImgButton,
|
||||
} from '@/hooks/Mapper/components/ui-kit';
|
||||
import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts';
|
||||
import { MouseEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { forwardRef, MouseEvent, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { getSystemById } from '@/hooks/Mapper/helpers/getSystemById.ts';
|
||||
import classes from './RoutesWidget.module.scss';
|
||||
import { useLoadRoutes } from './hooks';
|
||||
@@ -25,7 +26,10 @@ import {
|
||||
AddSystemDialog,
|
||||
SearchOnSubmitCallback,
|
||||
} from '@/hooks/Mapper/components/mapInterface/components/AddSystemDialog';
|
||||
import { OutCommand } from '@/hooks/Mapper/types';
|
||||
import {
|
||||
RoutesImperativeHandle,
|
||||
RoutesWidgetProps,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/types.ts';
|
||||
|
||||
const sortByDist = (a: Route, b: Route) => {
|
||||
const distA = a.has_connection ? a.systems?.length || 0 : Infinity;
|
||||
@@ -36,19 +40,16 @@ const sortByDist = (a: Route, b: Route) => {
|
||||
|
||||
export const RoutesWidgetContent = () => {
|
||||
const {
|
||||
data: { selectedSystems, hubs = [], systems, routes },
|
||||
outCommand,
|
||||
data: { selectedSystems, systems, isSubscriptionActive },
|
||||
} = useMapRootState();
|
||||
const { hubs = [], routesList, isRestricted } = useRouteProvider();
|
||||
|
||||
const [systemId] = selectedSystems;
|
||||
|
||||
const { loading } = useLoadRoutes();
|
||||
|
||||
const { systems: systemStatics, loadSystems, lastUpdateKey } = useLoadSystemStatic({ systems: hubs ?? [] });
|
||||
const { open, ...systemCtxProps } = useContextMenuSystemInfoHandlers({
|
||||
outCommand,
|
||||
hubs,
|
||||
});
|
||||
const { open, ...systemCtxProps } = useContextMenuSystemInfoHandlers();
|
||||
|
||||
const preparedHubs = useMemo(() => {
|
||||
return hubs.map(x => {
|
||||
@@ -61,20 +62,20 @@ export const RoutesWidgetContent = () => {
|
||||
|
||||
const preparedRoutes: Route[] = useMemo(() => {
|
||||
return (
|
||||
routes?.routes
|
||||
routesList?.routes
|
||||
.sort(sortByDist)
|
||||
.filter(x => x.destination.toString() !== systemId)
|
||||
// .filter(x => x.destination.toString() !== systemId)
|
||||
.map(route => ({
|
||||
...route,
|
||||
mapped_systems:
|
||||
route.systems?.map(solar_system_id =>
|
||||
routes?.systems_static_data.find(
|
||||
routesList?.systems_static_data.find(
|
||||
system_static_data => system_static_data.solar_system_id === solar_system_id,
|
||||
),
|
||||
) ?? [],
|
||||
})) ?? []
|
||||
);
|
||||
}, [routes?.routes, routes?.systems_static_data, systemId]);
|
||||
}, [routesList?.routes, routesList?.systems_static_data, systemId]);
|
||||
|
||||
const refData = useRef({ open, loadSystems, preparedRoutes });
|
||||
refData.current = { open, loadSystems, preparedRoutes };
|
||||
@@ -97,9 +98,13 @@ export const RoutesWidgetContent = () => {
|
||||
[handleClick],
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
if (isRestricted && !isSubscriptionActive) {
|
||||
return (
|
||||
<div className="w-full h-full flex justify-center items-center select-none text-center">Loading routes...</div>
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<span className="select-none text-center text-stone-400/80 text-sm">
|
||||
User Routes available with 'Active' map subscription only (contact map administrators)
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -117,7 +122,7 @@ export const RoutesWidgetContent = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{systemId !== undefined && routes && (
|
||||
<LoadingWrapper loading={loading}>
|
||||
<div className={clsx(classes.RoutesGrid, 'px-2 py-2')}>
|
||||
{preparedRoutes.map(route => {
|
||||
const sys = preparedHubs.find(x => x.solar_system_id === route.destination)!;
|
||||
@@ -132,7 +137,11 @@ export const RoutesWidgetContent = () => {
|
||||
<WdImgButton
|
||||
className={clsx(PrimeIcons.BARS, classes.RemoveBtn)}
|
||||
onClick={e => handleClick(e, route.destination.toString())}
|
||||
tooltip={{ content: 'Click here to open system menu', position: TooltipPosition.top, offset: 10 }}
|
||||
tooltip={{
|
||||
content: 'Click here to open system menu',
|
||||
position: TooltipPosition.top,
|
||||
offset: 10,
|
||||
}}
|
||||
/>
|
||||
|
||||
<SystemViewStandalone
|
||||
@@ -151,7 +160,7 @@ export const RoutesWidgetContent = () => {
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</LoadingWrapper>
|
||||
|
||||
<ContextMenuSystemInfo
|
||||
hubs={hubs}
|
||||
@@ -165,15 +174,13 @@ export const RoutesWidgetContent = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const RoutesWidgetComp = () => {
|
||||
const [routeSettingsVisible, setRouteSettingsVisible] = useState(false);
|
||||
const { data, update } = useRouteProvider();
|
||||
const {
|
||||
data: { hubs = [] },
|
||||
outCommand,
|
||||
} = useMapRootState();
|
||||
type RoutesWidgetCompProps = {
|
||||
title: ReactNode | string;
|
||||
};
|
||||
|
||||
const preparedHubs = useMemo(() => hubs.map(x => parseInt(x)), [hubs]);
|
||||
export const RoutesWidgetComp = ({ title }: RoutesWidgetCompProps) => {
|
||||
const [routeSettingsVisible, setRouteSettingsVisible] = useState(false);
|
||||
const { data, update, addHubCommand } = useRouteProvider();
|
||||
|
||||
const isSecure = data.path_type === 'secure';
|
||||
const handleSecureChange = useCallback(() => {
|
||||
@@ -190,24 +197,15 @@ export const RoutesWidgetComp = () => {
|
||||
const onAddSystem = useCallback(() => setOpenAddSystem(true), []);
|
||||
|
||||
const handleSubmitAddSystem: SearchOnSubmitCallback = useCallback(
|
||||
async item => {
|
||||
if (preparedHubs.includes(item.value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await outCommand({
|
||||
type: OutCommand.addHub,
|
||||
data: { system_id: item.value },
|
||||
});
|
||||
},
|
||||
[hubs, outCommand],
|
||||
async item => addHubCommand(item.value.toString()),
|
||||
[addHubCommand],
|
||||
);
|
||||
|
||||
return (
|
||||
<Widget
|
||||
label={
|
||||
<div className="flex justify-between items-center text-xs w-full" ref={ref}>
|
||||
<span className="select-none">Routes</span>
|
||||
<span className="select-none">{title}</span>
|
||||
<LayoutEventBlocker className="flex items-center gap-2">
|
||||
<WdImgButton
|
||||
className={PrimeIcons.PLUS_CIRCLE}
|
||||
@@ -231,6 +229,7 @@ export const RoutesWidgetComp = () => {
|
||||
className={PrimeIcons.SLIDERS_H}
|
||||
onClick={() => setRouteSettingsVisible(true)}
|
||||
tooltip={{
|
||||
position: TooltipPosition.top,
|
||||
content: 'Click here to open Routes settings',
|
||||
}}
|
||||
/>
|
||||
@@ -251,10 +250,13 @@ export const RoutesWidgetComp = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const RoutesWidget = () => {
|
||||
return (
|
||||
<RoutesProvider>
|
||||
<RoutesWidgetComp />
|
||||
</RoutesProvider>
|
||||
);
|
||||
};
|
||||
export const RoutesWidget = forwardRef<RoutesImperativeHandle, RoutesWidgetProps & RoutesWidgetCompProps>(
|
||||
({ title, ...props }, ref) => {
|
||||
return (
|
||||
<RoutesProvider {...props} ref={ref}>
|
||||
<RoutesWidgetComp title={title} />
|
||||
</RoutesProvider>
|
||||
);
|
||||
},
|
||||
);
|
||||
RoutesWidget.displayName = 'RoutesWidget';
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { OutCommand } from '@/hooks/Mapper/types';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import {
|
||||
RoutesType,
|
||||
useRouteProvider,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/RoutesProvider.tsx';
|
||||
import { useRouteProvider } from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/RoutesProvider.tsx';
|
||||
import { RoutesType } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
|
||||
function usePrevious<T>(value: T): T | undefined {
|
||||
const ref = useRef<T>();
|
||||
@@ -17,12 +14,10 @@ function usePrevious<T>(value: T): T | undefined {
|
||||
}
|
||||
|
||||
export const useLoadRoutes = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { data: routesSettings } = useRouteProvider();
|
||||
const { data: routesSettings, loadRoutesCommand, hubs, routesList, loading, setLoading } = useRouteProvider();
|
||||
|
||||
const {
|
||||
outCommand,
|
||||
data: { selectedSystems, hubs, systems, connections },
|
||||
data: { selectedSystems, systems, connections },
|
||||
} = useMapRootState();
|
||||
|
||||
const prevSys = usePrevious(systems);
|
||||
@@ -31,17 +26,16 @@ export const useLoadRoutes = () => {
|
||||
|
||||
const loadRoutes = useCallback(
|
||||
(systemId: string, routesSettings: RoutesType) => {
|
||||
outCommand({
|
||||
type: OutCommand.getRoutes,
|
||||
data: {
|
||||
system_id: systemId,
|
||||
routes_settings: routesSettings,
|
||||
},
|
||||
});
|
||||
loadRoutesCommand(systemId, routesSettings);
|
||||
setLoading(true);
|
||||
},
|
||||
[outCommand],
|
||||
[loadRoutesCommand],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(false);
|
||||
}, [routesList]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedSystems.length !== 1) {
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { RoutesType } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
import { RoutesList } from '@/hooks/Mapper/types/routes.ts';
|
||||
|
||||
export type LoadRoutesCommand = (systemId: string, routesSettings: RoutesType) => Promise<void>;
|
||||
export type AddHubCommand = (systemId: string) => Promise<void>;
|
||||
export type ToggleHubCommand = (systemId: string) => Promise<void>;
|
||||
|
||||
export type RoutesWidgetProps = {
|
||||
data: RoutesType;
|
||||
update: (d: RoutesType) => void;
|
||||
hubs: string[];
|
||||
routesList: RoutesList | undefined;
|
||||
|
||||
loadRoutesCommand: LoadRoutesCommand;
|
||||
addHubCommand: AddHubCommand;
|
||||
toggleHubCommand: ToggleHubCommand;
|
||||
isRestricted?: boolean;
|
||||
};
|
||||
|
||||
export type RoutesProviderInnerProps = RoutesWidgetProps & {
|
||||
loading: boolean;
|
||||
setLoading(loading: boolean): void;
|
||||
};
|
||||
|
||||
export type RoutesImperativeHandle = {
|
||||
stopLoading: () => void;
|
||||
};
|
||||
@@ -0,0 +1,83 @@
|
||||
import { Commands, OutCommand } from '@/hooks/Mapper/types';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import {
|
||||
AddHubCommand,
|
||||
LoadRoutesCommand,
|
||||
RoutesImperativeHandle,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/types.ts';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { RoutesWidget } from '@/hooks/Mapper/components/mapInterface/widgets';
|
||||
import { useMapEventListener } from '@/hooks/Mapper/events';
|
||||
|
||||
export const WRoutesPublic = () => {
|
||||
const {
|
||||
outCommand,
|
||||
storedSettings: { settingsRoutes, settingsRoutesUpdate },
|
||||
data: { hubs, routes },
|
||||
} = useMapRootState();
|
||||
|
||||
const ref = useRef<RoutesImperativeHandle>(null);
|
||||
|
||||
const loadRoutesCommand: LoadRoutesCommand = useCallback(
|
||||
async (systemId, routesSettings) => {
|
||||
outCommand({
|
||||
type: OutCommand.getRoutes,
|
||||
data: {
|
||||
system_id: systemId,
|
||||
routes_settings: routesSettings,
|
||||
},
|
||||
});
|
||||
},
|
||||
[outCommand],
|
||||
);
|
||||
|
||||
const addHubCommand: AddHubCommand = useCallback(
|
||||
async systemId => {
|
||||
if (hubs.includes(systemId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await outCommand({
|
||||
type: OutCommand.addHub,
|
||||
data: { system_id: systemId },
|
||||
});
|
||||
},
|
||||
[hubs, outCommand],
|
||||
);
|
||||
|
||||
const toggleHubCommand: AddHubCommand = useCallback(
|
||||
async (systemId: string | undefined) => {
|
||||
if (!systemId) {
|
||||
return;
|
||||
}
|
||||
|
||||
outCommand({
|
||||
type: !hubs.includes(systemId) ? OutCommand.addHub : OutCommand.deleteHub,
|
||||
data: {
|
||||
system_id: systemId,
|
||||
},
|
||||
});
|
||||
},
|
||||
[hubs, outCommand],
|
||||
);
|
||||
|
||||
useMapEventListener(event => {
|
||||
if (event.name === Commands.routes) {
|
||||
ref.current?.stopLoading();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<RoutesWidget
|
||||
ref={ref}
|
||||
title="Routes"
|
||||
data={settingsRoutes}
|
||||
update={settingsRoutesUpdate}
|
||||
hubs={hubs}
|
||||
routesList={routes}
|
||||
loadRoutesCommand={loadRoutesCommand}
|
||||
addHubCommand={addHubCommand}
|
||||
toggleHubCommand={toggleHubCommand}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './WRoutesPublic';
|
||||
@@ -0,0 +1,85 @@
|
||||
import { Commands, OutCommand } from '@/hooks/Mapper/types';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import {
|
||||
AddHubCommand,
|
||||
LoadRoutesCommand,
|
||||
RoutesImperativeHandle,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/types.ts';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { RoutesWidget } from '@/hooks/Mapper/components/mapInterface/widgets';
|
||||
import { useMapEventListener } from '@/hooks/Mapper/events';
|
||||
|
||||
export const WRoutesUser = () => {
|
||||
const {
|
||||
outCommand,
|
||||
storedSettings: { settingsRoutes, settingsRoutesUpdate },
|
||||
data: { userHubs, userRoutes },
|
||||
} = useMapRootState();
|
||||
|
||||
const ref = useRef<RoutesImperativeHandle>(null);
|
||||
|
||||
const loadRoutesCommand: LoadRoutesCommand = useCallback(
|
||||
async (systemId, routesSettings) => {
|
||||
outCommand({
|
||||
type: OutCommand.getUserRoutes,
|
||||
data: {
|
||||
system_id: systemId,
|
||||
routes_settings: routesSettings,
|
||||
},
|
||||
});
|
||||
},
|
||||
[outCommand],
|
||||
);
|
||||
|
||||
const addHubCommand: AddHubCommand = useCallback(
|
||||
async systemId => {
|
||||
if (userHubs.includes(systemId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await outCommand({
|
||||
type: OutCommand.addUserHub,
|
||||
data: { system_id: systemId },
|
||||
});
|
||||
},
|
||||
[userHubs, outCommand],
|
||||
);
|
||||
|
||||
const toggleHubCommand: AddHubCommand = useCallback(
|
||||
async (systemId: string | undefined) => {
|
||||
if (!systemId) {
|
||||
return;
|
||||
}
|
||||
|
||||
outCommand({
|
||||
type: !userHubs.includes(systemId) ? OutCommand.addUserHub : OutCommand.deleteUserHub,
|
||||
data: {
|
||||
system_id: systemId,
|
||||
},
|
||||
});
|
||||
},
|
||||
[userHubs, outCommand],
|
||||
);
|
||||
|
||||
useMapEventListener(event => {
|
||||
if (event.name === Commands.userRoutes) {
|
||||
ref.current?.stopLoading();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
return (
|
||||
<RoutesWidget
|
||||
ref={ref}
|
||||
title="User Routes"
|
||||
data={settingsRoutes}
|
||||
update={settingsRoutesUpdate}
|
||||
hubs={userHubs}
|
||||
routesList={userRoutes}
|
||||
loadRoutesCommand={loadRoutesCommand}
|
||||
addHubCommand={addHubCommand}
|
||||
toggleHubCommand={toggleHubCommand}
|
||||
isRestricted
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './WRoutesUser';
|
||||
@@ -4,3 +4,6 @@ export * from './RoutesWidget';
|
||||
export * from './SystemSignatures';
|
||||
export * from './SystemStructures';
|
||||
export * from './WSystemKills';
|
||||
export * from './WRoutesUser';
|
||||
export * from './WRoutesPublic';
|
||||
export * from './CommentsWidget';
|
||||
|
||||
@@ -18,7 +18,10 @@ export interface MapRootContentProps {}
|
||||
|
||||
// eslint-disable-next-line no-empty-pattern
|
||||
export const MapRootContent = ({}: MapRootContentProps) => {
|
||||
const { interfaceSettings, data } = useMapRootState();
|
||||
const {
|
||||
storedSettings: { interfaceSettings },
|
||||
data,
|
||||
} = useMapRootState();
|
||||
const { isShowMenu } = interfaceSettings;
|
||||
const { showCharacterActivity } = data;
|
||||
const { handleHideCharacterActivity } = useCharacterActivityHandlers();
|
||||
|
||||
@@ -31,13 +31,16 @@ type MapSettingsContextType = {
|
||||
const MapSettingsContext = createContext<MapSettingsContextType | undefined>(undefined);
|
||||
|
||||
export const MapSettingsProvider = ({ children }: { children: ReactNode }) => {
|
||||
const { outCommand, interfaceSettings, setInterfaceSettings } = useMapRootState();
|
||||
const {
|
||||
outCommand,
|
||||
storedSettings: { interfaceSettings, setInterfaceSettings },
|
||||
} = useMapRootState();
|
||||
|
||||
const [userRemoteSettings, setUserRemoteSettings] = useState<UserSettingsRemote>({
|
||||
...DEFAULT_REMOTE_SETTINGS,
|
||||
});
|
||||
|
||||
const mergedSettings = useMemo(() => {
|
||||
const mergedSettings: UserSettings = useMemo(() => {
|
||||
return {
|
||||
...userRemoteSettings,
|
||||
...interfaceSettings,
|
||||
@@ -75,7 +78,7 @@ export const MapSettingsProvider = ({ children }: { children: ReactNode }) => {
|
||||
if (item.type === 'checkbox') {
|
||||
return (
|
||||
<PrettySwitchbox
|
||||
key={item.prop}
|
||||
key={item.prop.toString()}
|
||||
label={item.label}
|
||||
checked={!!currentValue}
|
||||
setChecked={checked => handleSettingChange(item.prop, checked)}
|
||||
@@ -85,7 +88,7 @@ export const MapSettingsProvider = ({ children }: { children: ReactNode }) => {
|
||||
|
||||
if (item.type === 'dropdown' && item.options) {
|
||||
return (
|
||||
<div key={item.prop} className="flex items-center gap-2 mt-2">
|
||||
<div key={item.prop.toString()} className="flex items-center gap-2 mt-2">
|
||||
<label className="text-sm">{item.label}:</label>
|
||||
<Dropdown
|
||||
className="text-sm"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { SettingsListItem, UserSettingsRemoteProps } from './types.ts';
|
||||
import { AvailableThemes, InterfaceStoredSettingsProps } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { InterfaceStoredSettingsProps } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { AvailableThemes } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
|
||||
export const DEFAULT_REMOTE_SETTINGS = {
|
||||
[UserSettingsRemoteProps.link_signature_on_splash]: false,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
}
|
||||
|
||||
.SidebarOnTheMap {
|
||||
width: 400px;
|
||||
width: 500px;
|
||||
padding: 0 !important;
|
||||
|
||||
:global {
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import classes from './OnTheMap.module.scss';
|
||||
import { Sidebar } from 'primereact/sidebar';
|
||||
import { useMemo } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { sortCharacters } from '@/hooks/Mapper/components/mapInterface/helpers/sortCharacters.ts';
|
||||
import { VirtualScroller, VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
|
||||
import clsx from 'clsx';
|
||||
import { CharacterTypeRaw, WithIsOwnCharacter } from '@/hooks/Mapper/types';
|
||||
import { CharacterCard, WdCheckbox } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { CharacterCard, TooltipPosition, WdCheckbox, WdImageSize, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
|
||||
import useLocalStorageState from 'use-local-storage-state';
|
||||
import { useMapCheckPermissions, useMapGetOption } from '@/hooks/Mapper/mapRootProvider/hooks/api';
|
||||
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
|
||||
import { InputText } from 'primereact/inputtext';
|
||||
import { IconField } from 'primereact/iconfield';
|
||||
|
||||
type WindowLocalSettingsType = {
|
||||
compact: boolean;
|
||||
@@ -33,7 +35,7 @@ const itemTemplate = (item: CharacterTypeRaw & WithIsOwnCharacter, options: Virt
|
||||
})}
|
||||
style={{ height: options.props.itemSize + 'px' }}
|
||||
>
|
||||
<CharacterCard showSystem {...item} />
|
||||
<CharacterCard showCorporationLogo showAllyLogo showSystem showTicker {...item} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -48,6 +50,8 @@ export const OnTheMap = ({ show, onHide }: OnTheMapProps) => {
|
||||
data: { characters, userCharacters },
|
||||
} = useMapRootState();
|
||||
|
||||
const [searchVal, setSearchVal] = useState('');
|
||||
|
||||
const [settings, setSettings] = useLocalStorageState<WindowLocalSettingsType>('window:onTheMap:settings', {
|
||||
defaultValue: STORED_DEFAULT_VALUES,
|
||||
});
|
||||
@@ -61,13 +65,54 @@ export const OnTheMap = ({ show, onHide }: OnTheMapProps) => {
|
||||
);
|
||||
|
||||
const sorted = useMemo(() => {
|
||||
const out = characters.map(x => ({ ...x, isOwn: userCharacters.includes(x.eve_id) })).sort(sortCharacters);
|
||||
let out = characters.map(x => ({ ...x, isOwn: userCharacters.includes(x.eve_id) })).sort(sortCharacters);
|
||||
|
||||
if (searchVal !== '') {
|
||||
out = out.filter(x => {
|
||||
const normalized = searchVal.toLowerCase();
|
||||
|
||||
if (x.name.toLowerCase().includes(normalized)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (x.corporation_name.toLowerCase().includes(normalized)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (x.alliance_name?.toLowerCase().includes(normalized)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (x.corporation_ticker.toLowerCase().includes(normalized)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (x.alliance_ticker?.toLowerCase().includes(normalized)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (x.ship?.ship_name?.toLowerCase().includes(normalized)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (x.ship?.ship_type_info.name?.toLowerCase().includes(normalized)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (x.ship?.ship_type_info.group_name?.toLowerCase().includes(normalized)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
if (showOffline && !settings.hideOffline) {
|
||||
return out;
|
||||
}
|
||||
|
||||
return out.filter(x => x.online);
|
||||
}, [showOffline, characters, settings.hideOffline, userCharacters]);
|
||||
}, [showOffline, searchVal, characters, settings.hideOffline, userCharacters]);
|
||||
|
||||
return (
|
||||
<Sidebar
|
||||
@@ -79,7 +124,30 @@ export const OnTheMap = ({ show, onHide }: OnTheMapProps) => {
|
||||
icons={<></>}
|
||||
>
|
||||
<div className={clsx(classes.SidebarContent, '')}>
|
||||
<div className={'flex justify-end items-center gap-2 px-3'}>
|
||||
<div className={'flex justify-between items-center gap-2 px-2 pt-1'}>
|
||||
<IconField>
|
||||
{searchVal.length > 0 && (
|
||||
<WdImgButton
|
||||
className="pi pi-trash"
|
||||
textSize={WdImageSize.large}
|
||||
tooltip={{
|
||||
content: 'Clear',
|
||||
className: 'pi p-input-icon',
|
||||
position: TooltipPosition.top,
|
||||
}}
|
||||
onClick={() => setSearchVal('')}
|
||||
/>
|
||||
)}
|
||||
<InputText
|
||||
id="label"
|
||||
aria-describedby="label"
|
||||
autoComplete="off"
|
||||
value={searchVal}
|
||||
placeholder="Type to search"
|
||||
onChange={e => setSearchVal(e.target.value)}
|
||||
/>
|
||||
</IconField>
|
||||
|
||||
{showOffline && (
|
||||
<WdCheckbox
|
||||
size="m"
|
||||
|
||||
@@ -15,7 +15,9 @@ interface RightBarProps {
|
||||
}
|
||||
|
||||
export const RightBar = ({ onShowOnTheMap, onShowMapSettings, onShowTrackingDialog }: RightBarProps) => {
|
||||
const { interfaceSettings, setInterfaceSettings } = useMapRootState();
|
||||
const {
|
||||
storedSettings: { interfaceSettings, setInterfaceSettings },
|
||||
} = useMapRootState();
|
||||
|
||||
const canTrackCharacters = useMapCheckPermissions([UserPermission.TRACK_CHARACTER]);
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ export const TrackingCharactersList = () => {
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
headerClassName="[&_div]:ml-2"
|
||||
body={row => {
|
||||
return <CharacterCard showShipName={false} showSystem={false} isOwn {...row.character} />;
|
||||
return <CharacterCard showCorporationLogo showTicker isOwn {...row.character} />;
|
||||
}}
|
||||
/>
|
||||
</DataTable>
|
||||
|
||||
@@ -10,8 +10,8 @@ const renderValCharacterTemplate = (row: TrackingCharacter | undefined) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="py-1">
|
||||
<CharacterCard compact showShipName={false} showSystem={false} isOwn {...row.character} />
|
||||
<div className="py-1 w-full">
|
||||
<CharacterCard compact isOwn {...row.character} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -21,7 +21,11 @@ const renderCharacterTemplate = (row: TrackingCharacter | undefined) => {
|
||||
return <div className="h-[33px] flex items-center">Character is not selected</div>;
|
||||
}
|
||||
|
||||
return <CharacterCard showShipName={false} showSystem={false} isOwn {...row.character} />;
|
||||
return (
|
||||
<div className="w-full">
|
||||
<CharacterCard isOwn {...row.character} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const TrackingSettings = () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
|
||||
import type { ActivitySummary } from '@/hooks/Mapper/components/mapRootContent/components/CharacterActivity/CharacterActivity';
|
||||
import { ActivitySummary } from '@/hooks/Mapper/types';
|
||||
|
||||
/**
|
||||
* Hook for character activity related handlers
|
||||
|
||||
@@ -20,7 +20,6 @@ import { Node, useReactFlow, XYPosition } from 'reactflow';
|
||||
import { useCommandsSystems } from '@/hooks/Mapper/mapRootProvider/hooks/api';
|
||||
import { emitMapEvent, useMapEventListener } from '@/hooks/Mapper/events';
|
||||
|
||||
import { STORED_INTERFACE_DEFAULT_VALUES } from '@/hooks/Mapper/mapRootProvider/MapRootProvider';
|
||||
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
|
||||
import { useCommonMapEventProcessor } from '@/hooks/Mapper/components/mapWrapper/hooks/useCommonMapEventProcessor.ts';
|
||||
import {
|
||||
@@ -28,30 +27,34 @@ import {
|
||||
SearchOnSubmitCallback,
|
||||
} from '@/hooks/Mapper/components/mapInterface/components/AddSystemDialog';
|
||||
import { useHotkey } from '../../hooks/useHotkey';
|
||||
import { STORED_INTERFACE_DEFAULT_VALUES } from '@/hooks/Mapper/mapRootProvider/constants.ts';
|
||||
|
||||
// TODO: INFO - this component needs for abstract work with Map instance
|
||||
export const MapWrapper = () => {
|
||||
const {
|
||||
update,
|
||||
outCommand,
|
||||
data: { selectedConnections, selectedSystems, hubs, systems, linkSignatureToSystem, systemSignatures },
|
||||
interfaceSettings: {
|
||||
isShowMenu,
|
||||
isShowMinimap = STORED_INTERFACE_DEFAULT_VALUES.isShowMinimap,
|
||||
isShowKSpace,
|
||||
isThickConnections,
|
||||
isShowBackgroundPattern,
|
||||
isShowUnsplashedSignatures,
|
||||
isSoftBackground,
|
||||
theme,
|
||||
},
|
||||
data: { selectedConnections, selectedSystems, hubs, userHubs, systems, linkSignatureToSystem, systemSignatures },
|
||||
storedSettings: { interfaceSettings },
|
||||
} = useMapRootState();
|
||||
|
||||
const {
|
||||
isShowMenu,
|
||||
isShowMinimap = STORED_INTERFACE_DEFAULT_VALUES.isShowMinimap,
|
||||
isShowKSpace,
|
||||
isThickConnections,
|
||||
isShowBackgroundPattern,
|
||||
isShowUnsplashedSignatures,
|
||||
isSoftBackground,
|
||||
theme,
|
||||
} = interfaceSettings;
|
||||
|
||||
const { deleteSystems } = useDeleteSystems();
|
||||
const { mapRef, runCommand } = useCommonMapEventProcessor();
|
||||
const { getNodes } = useReactFlow();
|
||||
|
||||
const { updateLinkSignatureToSystem } = useCommandsSystems();
|
||||
const { open, ...systemContextProps } = useContextMenuSystemHandlers({ systems, hubs, outCommand });
|
||||
const { open, ...systemContextProps } = useContextMenuSystemHandlers({ systems, hubs, userHubs, outCommand });
|
||||
const { handleSystemMultipleContext, ...systemMultipleCtxProps } = useContextMenuSystemMultipleHandlers();
|
||||
|
||||
const [openSettings, setOpenSettings] = useState<string | null>(null);
|
||||
@@ -107,17 +110,20 @@ export const MapWrapper = () => {
|
||||
[outCommand],
|
||||
);
|
||||
|
||||
const handleSystemContextMenu = useCallback((ev: any, systemId: string) => {
|
||||
const { selectedSystems, systems } = ref.current;
|
||||
if (selectedSystems.length > 1) {
|
||||
const systemsInfo: Node[] = selectedSystems.map(x => ({ data: getSystemById(systems, x), id: x }) as Node);
|
||||
const handleSystemContextMenu = useCallback(
|
||||
(ev: any, systemId: string) => {
|
||||
const { selectedSystems, systems } = ref.current;
|
||||
if (selectedSystems.length > 1) {
|
||||
const systemsInfo: Node[] = selectedSystems.map(x => ({ data: getSystemById(systems, x), id: x }) as Node);
|
||||
|
||||
handleSystemMultipleContext(ev, systemsInfo);
|
||||
return;
|
||||
}
|
||||
handleSystemMultipleContext(ev, systemsInfo);
|
||||
return;
|
||||
}
|
||||
|
||||
open(ev, systemId);
|
||||
}, []);
|
||||
open(ev, systemId);
|
||||
},
|
||||
[handleSystemMultipleContext, open],
|
||||
);
|
||||
|
||||
const handleConnectionDbClick = useCallback((e: SolarSystemConnection) => setSelectedConnection(e), []);
|
||||
|
||||
@@ -215,6 +221,7 @@ export const MapWrapper = () => {
|
||||
<ContextMenuSystem
|
||||
systems={systems}
|
||||
hubs={hubs}
|
||||
userHubs={userHubs}
|
||||
{...systemContextProps}
|
||||
onOpenSettings={() => {
|
||||
systemContextProps.systemId && setOpenSettings(systemContextProps.systemId);
|
||||
|
||||
@@ -4,15 +4,24 @@ import { SystemView } from '@/hooks/Mapper/components/ui-kit/SystemView';
|
||||
import { CharacterTypeRaw, WithIsOwnCharacter } from '@/hooks/Mapper/types';
|
||||
import { Commands } from '@/hooks/Mapper/types/mapHandlers';
|
||||
import { emitMapEvent } from '@/hooks/Mapper/events';
|
||||
import { CharacterPortrait, CharacterPortraitSize } from '@/hooks/Mapper/components/ui-kit';
|
||||
import {
|
||||
TooltipPosition,
|
||||
WdEveEntityPortrait,
|
||||
WdEveEntityPortraitSize,
|
||||
WdEveEntityPortraitType,
|
||||
WdTooltipWrapper,
|
||||
} from '@/hooks/Mapper/components/ui-kit';
|
||||
import { isDocked } from '@/hooks/Mapper/helpers/isDocked.ts';
|
||||
import classes from './CharacterCard.module.scss';
|
||||
|
||||
type CharacterCardProps = {
|
||||
compact?: boolean;
|
||||
showSystem?: boolean;
|
||||
showTicker?: boolean;
|
||||
showShipName?: boolean;
|
||||
useSystemsCache?: boolean;
|
||||
showCorporationLogo?: boolean;
|
||||
showAllyLogo?: boolean;
|
||||
} & CharacterTypeRaw &
|
||||
WithIsOwnCharacter;
|
||||
|
||||
@@ -29,6 +38,9 @@ export const CharacterCard = ({
|
||||
isOwn,
|
||||
showSystem,
|
||||
showShipName,
|
||||
showCorporationLogo,
|
||||
showAllyLogo,
|
||||
showTicker,
|
||||
useSystemsCache,
|
||||
...char
|
||||
}: CharacterCardProps) => {
|
||||
@@ -46,26 +58,80 @@ export const CharacterCard = ({
|
||||
|
||||
if (compact) {
|
||||
return (
|
||||
<div className={clsx('w-full text-xs box-border')} onClick={handleSelect}>
|
||||
<div className="text-xs box-border w-full" onClick={handleSelect}>
|
||||
<div className="w-full flex items-center gap-1 relative">
|
||||
<CharacterPortrait characterEveId={char.eve_id} size={CharacterPortraitSize.w18} />
|
||||
<WdEveEntityPortrait eveId={char.eve_id} size={WdEveEntityPortraitSize.w18} />
|
||||
|
||||
{showCorporationLogo && (
|
||||
<WdTooltipWrapper position={TooltipPosition.top} content={char.corporation_name}>
|
||||
<WdEveEntityPortrait
|
||||
type={WdEveEntityPortraitType.corporation}
|
||||
eveId={char.corporation_id.toString()}
|
||||
size={WdEveEntityPortraitSize.w18}
|
||||
/>
|
||||
</WdTooltipWrapper>
|
||||
)}
|
||||
|
||||
{showAllyLogo && char.alliance_id && (
|
||||
<WdTooltipWrapper position={TooltipPosition.top} content={char.alliance_name}>
|
||||
<WdEveEntityPortrait
|
||||
type={WdEveEntityPortraitType.alliance}
|
||||
eveId={char.alliance_id.toString()}
|
||||
size={WdEveEntityPortraitSize.w18}
|
||||
/>
|
||||
</WdTooltipWrapper>
|
||||
)}
|
||||
|
||||
{isDocked(char.location) && <span className={classes.Docked} />}
|
||||
<div className="flex flex-grow overflow-hidden text-left">
|
||||
<div className="overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
<span className={clsx(isOwn ? 'text-orange-400' : 'text-gray-200')}>{char.name}</span>{' '}
|
||||
<span className="text-gray-400">
|
||||
{!locationShown && showShipName && shipNameText ? `- ${shipNameText}` : `[${tickerText}]`}
|
||||
<div className="flex flex-grow-[2] overflow-hidden text-left w-[50px]">
|
||||
<div className="flex min-w-0">
|
||||
<span
|
||||
className={clsx(
|
||||
'overflow-hidden text-ellipsis whitespace-nowrap',
|
||||
isOwn ? 'text-orange-400' : 'text-gray-200',
|
||||
)}
|
||||
title={char.name}
|
||||
>
|
||||
{char.name}
|
||||
</span>
|
||||
{showTicker && <span className="flex-shrink-0 text-gray-400 ml-1">[{tickerText}]</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{shipType && (
|
||||
<div
|
||||
className="text-gray-300 overflow-hidden text-ellipsis whitespace-nowrap flex-shrink-0"
|
||||
style={{ maxWidth: '120px' }}
|
||||
title={shipType}
|
||||
>
|
||||
{shipType}
|
||||
</div>
|
||||
<>
|
||||
{!showShipName && (
|
||||
<div
|
||||
className="text-gray-300 overflow-hidden text-ellipsis whitespace-nowrap flex-shrink-0"
|
||||
style={{ maxWidth: '120px' }}
|
||||
title={shipType}
|
||||
>
|
||||
{shipType}
|
||||
</div>
|
||||
)}
|
||||
{showShipName && (
|
||||
<div className="flex flex-grow-[1] justify-end w-[50px]">
|
||||
<div className="min-w-0">
|
||||
<div
|
||||
className="text-gray-300 overflow-hidden text-ellipsis whitespace-nowrap flex-shrink-0"
|
||||
style={{ maxWidth: '120px' }}
|
||||
title={shipNameText}
|
||||
>
|
||||
{shipNameText}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{char.ship && (
|
||||
<WdTooltipWrapper position={TooltipPosition.top} content={char.ship.ship_type_info.name}>
|
||||
<WdEveEntityPortrait
|
||||
type={WdEveEntityPortraitType.ship}
|
||||
eveId={char.ship.ship_type_id.toString()}
|
||||
size={WdEveEntityPortraitSize.w18}
|
||||
/>
|
||||
</WdTooltipWrapper>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -75,11 +141,41 @@ export const CharacterCard = ({
|
||||
return (
|
||||
<div className={clsx('w-full text-xs box-border')} onClick={handleSelect}>
|
||||
<div className="w-full flex items-center gap-2">
|
||||
<CharacterPortrait characterEveId={char.eve_id} size={CharacterPortraitSize.w33} />
|
||||
<div className="flex flex-col flex-grow overflow-hidden">
|
||||
<div className="overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
<span className={clsx(isOwn ? 'text-orange-400' : 'text-gray-200')}>{char.name}</span>{' '}
|
||||
<span className="text-gray-400">[{tickerText}]</span>
|
||||
<div className="flex items-center gap-1">
|
||||
<WdEveEntityPortrait eveId={char.eve_id} size={WdEveEntityPortraitSize.w33} />
|
||||
|
||||
{showCorporationLogo && (
|
||||
<WdTooltipWrapper position={TooltipPosition.top} content={char.corporation_name}>
|
||||
<WdEveEntityPortrait
|
||||
type={WdEveEntityPortraitType.corporation}
|
||||
eveId={char.corporation_id.toString()}
|
||||
size={WdEveEntityPortraitSize.w33}
|
||||
/>
|
||||
</WdTooltipWrapper>
|
||||
)}
|
||||
|
||||
{showAllyLogo && char.alliance_id && (
|
||||
<WdTooltipWrapper position={TooltipPosition.top} content={char.alliance_name}>
|
||||
<WdEveEntityPortrait
|
||||
type={WdEveEntityPortraitType.alliance}
|
||||
eveId={char.alliance_id.toString()}
|
||||
size={WdEveEntityPortraitSize.w33}
|
||||
/>
|
||||
</WdTooltipWrapper>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col flex-grow overflow-hidden w-[50px]">
|
||||
<div className="flex min-w-0">
|
||||
<span
|
||||
className={clsx(
|
||||
'overflow-hidden text-ellipsis whitespace-nowrap',
|
||||
isOwn ? 'text-orange-400' : 'text-gray-200',
|
||||
)}
|
||||
>
|
||||
{char.name}
|
||||
</span>
|
||||
{showTicker && <span className="flex-shrink-0 text-gray-400 ml-1">[{tickerText}]</span>}
|
||||
</div>
|
||||
{locationShown ? (
|
||||
<div className="text-gray-300 text-xs overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
@@ -97,15 +193,30 @@ export const CharacterCard = ({
|
||||
)}
|
||||
</div>
|
||||
{shipType && (
|
||||
<div className="flex-shrink-0 self-start">
|
||||
<div
|
||||
className="text-gray-300 overflow-hidden text-ellipsis whitespace-nowrap"
|
||||
style={{ maxWidth: '200px' }}
|
||||
title={shipType}
|
||||
>
|
||||
{shipType}
|
||||
<>
|
||||
<div className="flex flex-col flex-shrink-0 items-end self-start">
|
||||
<div
|
||||
className="text-gray-300 overflow-hidden text-ellipsis whitespace-nowrap max-w-[200px]"
|
||||
title={shipType}
|
||||
>
|
||||
{shipType}
|
||||
</div>
|
||||
<div
|
||||
className="flex justify-end text-stone-500 overflow-hidden text-ellipsis whitespace-nowrap max-w-[200px]"
|
||||
title={shipNameText}
|
||||
>
|
||||
{shipNameText}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{char.ship && (
|
||||
<WdEveEntityPortrait
|
||||
type={WdEveEntityPortraitType.ship}
|
||||
eveId={char.ship.ship_type_id.toString()}
|
||||
size={WdEveEntityPortraitSize.w33}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import clsx from 'clsx';
|
||||
import { WithClassName } from '@/hooks/Mapper/types/common.ts';
|
||||
|
||||
export enum CharacterPortraitSize {
|
||||
default,
|
||||
w18,
|
||||
w33,
|
||||
}
|
||||
|
||||
// TODO IF YOU NEED ANOTHER ONE SIZE PLEASE ADD IT HERE and IN CharacterPortraitSize
|
||||
const getSize = (size: CharacterPortraitSize) => {
|
||||
switch (size) {
|
||||
case CharacterPortraitSize.w18:
|
||||
return 'min-w-[18px] min-h-[18px] w-[18px] h-[18px]';
|
||||
case CharacterPortraitSize.w33:
|
||||
return 'min-w-[33px] min-h-[33px] w-[33px] h-[33px]';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
export type CharacterPortraitProps = {
|
||||
characterEveId: string | undefined;
|
||||
size?: CharacterPortraitSize;
|
||||
} & WithClassName;
|
||||
|
||||
export const CharacterPortrait = ({
|
||||
characterEveId,
|
||||
size = CharacterPortraitSize.default,
|
||||
className,
|
||||
}: CharacterPortraitProps) => {
|
||||
if (characterEveId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<span
|
||||
className={clsx(
|
||||
getSize(size),
|
||||
'flex transition-[border-color,opacity] duration-250 border border-gray-800 bg-transparent rounded-none',
|
||||
'wd-bg-default',
|
||||
className,
|
||||
)}
|
||||
style={{ backgroundImage: `url(https://images.evetech.net/characters/${characterEveId}/portrait)` }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from './CharacterPortrait';
|
||||
25
assets/js/hooks/Mapper/components/ui-kit/LoadingWrapper.tsx
Normal file
25
assets/js/hooks/Mapper/components/ui-kit/LoadingWrapper.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
import { ProgressSpinner } from 'primereact/progressspinner';
|
||||
|
||||
type LoadingWrapperProps = {
|
||||
loading?: boolean;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export const LoadingWrapper: React.FC<LoadingWrapperProps> = ({ loading, children }) => {
|
||||
return (
|
||||
<div className="relative w-full h-full">
|
||||
{children}
|
||||
{loading && (
|
||||
<div className="absolute inset-0 bg-stone-950/50 flex items-center justify-center z-10">
|
||||
<ProgressSpinner
|
||||
style={{ width: '50px', height: '50px' }}
|
||||
strokeWidth="2"
|
||||
fill="transparent"
|
||||
animationDuration="2s"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
29
assets/js/hooks/Mapper/components/ui-kit/SvgIconWrapper.tsx
Normal file
29
assets/js/hooks/Mapper/components/ui-kit/SvgIconWrapper.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
|
||||
export type SvgIconProps = React.SVGAttributes<SVGElement> & {
|
||||
width?: number;
|
||||
height?: number;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const SvgIconWrapper = ({
|
||||
width = 24,
|
||||
height = 24,
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: SvgIconProps & { children: React.ReactNode }) => {
|
||||
return (
|
||||
<svg
|
||||
width={width}
|
||||
height={height}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={clsx('w-[19px] h-[19px]', className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
import clsx from 'clsx';
|
||||
import { WithClassName } from '@/hooks/Mapper/types/common.ts';
|
||||
|
||||
export enum WdEveEntityPortraitType {
|
||||
character,
|
||||
corporation,
|
||||
alliance,
|
||||
ship,
|
||||
}
|
||||
|
||||
export enum WdEveEntityPortraitSize {
|
||||
default,
|
||||
w18,
|
||||
w33,
|
||||
}
|
||||
|
||||
export const getLogo = (type: WdEveEntityPortraitType, eveId: string | number) => {
|
||||
switch (type) {
|
||||
case WdEveEntityPortraitType.alliance:
|
||||
return `url(https://images.evetech.net/alliances/${eveId}/logo?size=64)`;
|
||||
case WdEveEntityPortraitType.corporation:
|
||||
return `url(https://images.evetech.net/corporations/${eveId}/logo?size=64)`;
|
||||
case WdEveEntityPortraitType.character:
|
||||
return `url(https://images.evetech.net/characters/${eveId}/portrait)`;
|
||||
case WdEveEntityPortraitType.ship:
|
||||
return `url(https://images.evetech.net/types/${eveId}/icon)`;
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
// TODO IF YOU NEED ANOTHER ONE SIZE PLEASE ADD IT HERE and IN WdEveEntityPortraitSize
|
||||
const getSize = (size: WdEveEntityPortraitSize) => {
|
||||
switch (size) {
|
||||
case WdEveEntityPortraitSize.w18:
|
||||
return 'min-w-[18px] min-h-[18px] w-[18px] h-[18px]';
|
||||
case WdEveEntityPortraitSize.w33:
|
||||
return 'min-w-[33px] min-h-[33px] w-[33px] h-[33px]';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
export type WdEveEntityPortraitProps = {
|
||||
eveId: string | undefined;
|
||||
type?: WdEveEntityPortraitType;
|
||||
size?: WdEveEntityPortraitSize;
|
||||
} & WithClassName;
|
||||
|
||||
export const WdEveEntityPortrait = ({
|
||||
eveId,
|
||||
size = WdEveEntityPortraitSize.default,
|
||||
type = WdEveEntityPortraitType.character,
|
||||
className,
|
||||
}: WdEveEntityPortraitProps) => {
|
||||
if (eveId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<span
|
||||
className={clsx(
|
||||
getSize(size),
|
||||
'flex transition-[border-color,opacity] duration-250 border border-gray-800 bg-transparent rounded-none',
|
||||
'wd-bg-default',
|
||||
className,
|
||||
)}
|
||||
style={{ backgroundImage: getLogo(type, eveId) }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './WdEveEntityPortrait.tsx';
|
||||
@@ -14,5 +14,6 @@ export * from './TimeAgo';
|
||||
export * from './WdTooltipWrapper';
|
||||
export * from './WdResponsiveCheckBox';
|
||||
export * from './WdRadioButton';
|
||||
export * from './CharacterPortrait';
|
||||
export * from './WdEveEntityPortrait';
|
||||
export * from './WdTransition';
|
||||
export * from './LoadingWrapper';
|
||||
|
||||
@@ -2,3 +2,4 @@ export * from './usePageVisibility';
|
||||
export * from './useClipboard';
|
||||
export * from './useHotkey';
|
||||
export * from './useSkipContextMenu';
|
||||
export * from './useActualizeSettings';
|
||||
|
||||
23
assets/js/hooks/Mapper/hooks/useActualizeSettings.ts
Normal file
23
assets/js/hooks/Mapper/hooks/useActualizeSettings.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
type Settings = Record<string, unknown>;
|
||||
export const useActualizeSettings = <T extends Settings>(defaultVals: T, vals: T, setVals: (newVals: T) => void) => {
|
||||
useEffect(() => {
|
||||
let foundNew = false;
|
||||
const newVals = Object.keys(defaultVals).reduce((acc, x) => {
|
||||
if (Object.keys(acc).includes(x)) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
foundNew = true;
|
||||
|
||||
// @ts-ignore
|
||||
return { ...acc, [x]: defaultVals[x] };
|
||||
}, vals);
|
||||
|
||||
if (foundNew) {
|
||||
setVals(newVals);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
};
|
||||
@@ -1,7 +1,8 @@
|
||||
import { AvailableThemes, useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { AvailableThemes } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
|
||||
export const useTheme = (): AvailableThemes => {
|
||||
const { interfaceSettings } = useMapRootState();
|
||||
const { storedSettings } = useMapRootState();
|
||||
|
||||
return interfaceSettings.theme;
|
||||
return storedSettings.interfaceSettings.theme;
|
||||
};
|
||||
|
||||
18
assets/js/hooks/Mapper/icons/MapAddIcon.tsx
Normal file
18
assets/js/hooks/Mapper/icons/MapAddIcon.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { SvgIconProps, SvgIconWrapper } from '@/hooks/Mapper/components/ui-kit/SvgIconWrapper.tsx';
|
||||
|
||||
export const MapAddIcon = (props: SvgIconProps) => (
|
||||
<SvgIconWrapper width={800} height={800} viewBox="0 0 800 800" {...props}>
|
||||
<path
|
||||
d="M416.667 234.716C411.247 233.807 405.68 233.333 400 233.333C344.77 233.333 300 278.105 300 333.333C300 388.563 344.77 433.333 400 433.333C455.23 433.333 500 388.563 500 333.333C500 327.655 499.527 322.087 498.617 316.667"
|
||||
stroke="currentColor"
|
||||
strokeWidth="50"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M166.667 507.203C145.085 452.073 133.333 393.377 133.333 338.11C133.333 188.196 252.724 66.6667 400 66.6667C547.277 66.6667 666.667 188.196 666.667 338.11C666.667 486.85 581.557 660.413 448.763 722.48C417.81 736.95 382.19 736.95 351.237 722.48C308.825 702.657 271.277 671.463 239.813 633.333"
|
||||
stroke="currentColor"
|
||||
strokeWidth="50"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</SvgIconWrapper>
|
||||
);
|
||||
19
assets/js/hooks/Mapper/icons/MapDeleteIcon.tsx
Normal file
19
assets/js/hooks/Mapper/icons/MapDeleteIcon.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { SvgIconProps, SvgIconWrapper } from '@/hooks/Mapper/components/ui-kit/SvgIconWrapper.tsx';
|
||||
|
||||
export const MapDeleteIcon = (props: SvgIconProps) => (
|
||||
<SvgIconWrapper width={800} height={800} viewBox="0 0 800 800" {...props}>
|
||||
<path
|
||||
d="M416.667 234.716C411.247 233.807 405.68 233.333 400 233.333C344.77 233.333 300 278.105 300 333.333C300 388.563 344.77 433.333 400 433.333C455.23 433.333 500 388.563 500 333.333C500 327.655 499.527 322.087 498.617 316.667"
|
||||
stroke="currentColor"
|
||||
strokeWidth="50"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M166.667 507.203C145.085 452.073 133.333 393.377 133.333 338.11C133.333 188.196 252.724 66.6666 400 66.6666C547.277 66.6666 666.667 188.196 666.667 338.11C666.667 486.85 581.557 660.413 448.763 722.48C417.81 736.95 382.19 736.95 351.237 722.48C308.825 702.657 271.277 671.463 239.813 633.333"
|
||||
stroke="currentColor"
|
||||
strokeWidth="50"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path d="M747.901 90L105 732.9" stroke="currentColor" strokeWidth="63" strokeLinecap="round" />
|
||||
</SvgIconWrapper>
|
||||
);
|
||||
14
assets/js/hooks/Mapper/icons/MapUserAddIcon.tsx
Normal file
14
assets/js/hooks/Mapper/icons/MapUserAddIcon.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { SvgIconProps, SvgIconWrapper } from '@/hooks/Mapper/components/ui-kit/SvgIconWrapper.tsx';
|
||||
|
||||
export const MapUserAddIcon = (props: SvgIconProps) => (
|
||||
<SvgIconWrapper width={800} height={800} viewBox="0 0 800 800" {...props}>
|
||||
<path
|
||||
d="M259.095 617.422C288.199 652.691 322.293 680.967 360.022 698.982L361.822 699.832L361.823 699.833L362.963 700.355C371.571 704.236 380.618 706.7 389.783 707.749L392.258 758.116C374.601 757.137 357.082 752.809 340.65 745.128V745.129C294.272 723.452 253.892 689.676 220.529 649.245L239.812 633.334L259.095 617.422ZM223.9 614.051C234.55 605.263 250.307 606.773 259.095 617.422L220.529 649.245C211.742 638.596 213.251 622.839 223.9 614.051ZM400 41.667C515.7 41.6671 615.317 110.002 662.514 208.804C653.511 207.614 644.327 207 635 207C625.128 207 615.417 207.686 605.911 209.017C563.301 138.509 486.858 91.6671 400 91.667C266.948 91.6672 158.333 201.583 158.333 338.11C158.333 389.156 169.046 443.842 188.989 495.627L189.946 498.09L190.174 498.694C194.779 511.398 188.435 525.529 175.779 530.483C163.123 535.437 148.873 529.369 143.63 516.915L143.387 516.317L142.334 513.607C120.393 456.639 108.333 395.871 108.333 338.11C108.333 174.81 238.5 41.6672 400 41.667ZM400 208.334C406.63 208.334 413.153 208.852 419.529 209.854L420.803 210.061L421.438 210.176C434.704 212.748 443.57 225.448 441.321 238.853C439.072 252.257 426.546 261.369 413.167 259.471L412.529 259.372L411.768 259.248C407.948 258.648 404.02 258.334 400 258.334C358.578 258.334 325 291.913 325 333.334C325 374.756 358.577 408.334 400 408.334C409.643 408.334 418.861 406.513 427.329 403.198C427.111 407.104 427 411.039 427 415C427 428.513 428.289 441.725 430.75 454.521C420.913 457.009 410.611 458.334 400 458.334C330.963 458.334 275 402.371 275 333.334C275 264.299 330.963 208.334 400 208.334Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M661.071 406.777C661.071 381.914 640.868 361.666 615.833 361.666C590.798 361.666 570.595 381.914 570.595 406.777C570.595 431.641 590.798 451.889 615.833 451.889C640.868 451.889 661.071 431.641 661.071 406.777ZM727.737 406.777C727.737 457.286 694.219 499.913 648.248 513.79C727.063 528.912 786.666 598.136 786.666 681.333C786.666 699.742 771.742 714.666 753.333 714.666C734.924 714.666 720 699.742 720 681.333C720 623.977 673.414 577.389 615.833 577.389C558.253 577.389 511.666 623.977 511.666 681.333C511.666 699.742 496.742 714.666 478.333 714.666C459.924 714.666 445 699.742 445 681.333C445 598.136 504.603 528.912 583.417 513.79C537.446 499.913 503.929 457.286 503.929 406.777C503.929 344.993 554.081 295 615.833 295C677.584 295 727.737 344.993 727.737 406.777Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</SvgIconWrapper>
|
||||
);
|
||||
15
assets/js/hooks/Mapper/icons/MapUserDeleteIcon.tsx
Normal file
15
assets/js/hooks/Mapper/icons/MapUserDeleteIcon.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { SvgIconProps, SvgIconWrapper } from '@/hooks/Mapper/components/ui-kit/SvgIconWrapper.tsx';
|
||||
|
||||
export const MapUserDeleteIcon = (props: SvgIconProps) => (
|
||||
<SvgIconWrapper width={800} height={800} viewBox="0 0 800 800" {...props}>
|
||||
<path
|
||||
d="M259.095 617.422C288.199 652.691 322.293 680.967 360.022 698.982L361.822 699.832L361.823 699.833L362.963 700.355C371.571 704.236 380.618 706.7 389.783 707.749L392.258 758.116C374.601 757.137 357.082 752.809 340.65 745.128V745.129C294.272 723.452 253.892 689.676 220.529 649.245L239.812 633.334L259.095 617.422ZM223.9 614.051C234.55 605.263 250.307 606.773 259.095 617.422L220.529 649.245C211.742 638.596 213.251 622.839 223.9 614.051ZM400 41.667C515.7 41.6671 615.317 110.002 662.514 208.804C653.511 207.614 644.327 207 635 207C625.128 207 615.417 207.686 605.911 209.017C563.301 138.509 486.858 91.6671 400 91.667C266.948 91.6672 158.333 201.583 158.333 338.11C158.333 389.156 169.046 443.842 188.989 495.627L189.946 498.09L190.174 498.694C194.779 511.398 188.435 525.529 175.779 530.483C163.123 535.437 148.873 529.369 143.63 516.915L143.387 516.317L142.334 513.607C120.393 456.639 108.333 395.871 108.333 338.11C108.333 174.81 238.5 41.6672 400 41.667ZM400 208.334C406.63 208.334 413.153 208.852 419.529 209.854L420.803 210.061L421.438 210.176C434.704 212.748 443.57 225.448 441.321 238.853C439.072 252.257 426.546 261.369 413.167 259.471L412.529 259.372L411.768 259.248C407.948 258.648 404.02 258.334 400 258.334C358.578 258.334 325 291.913 325 333.334C325 374.756 358.577 408.334 400 408.334C409.643 408.334 418.861 406.513 427.329 403.198C427.111 407.104 427 411.039 427 415C427 428.513 428.289 441.725 430.75 454.521C420.913 457.009 410.611 458.334 400 458.334C330.963 458.334 275 402.371 275 333.334C275 264.299 330.963 208.334 400 208.334Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M661.071 406.777C661.071 381.914 640.868 361.666 615.833 361.666C590.798 361.666 570.595 381.914 570.595 406.777C570.595 431.641 590.798 451.889 615.833 451.889C640.868 451.889 661.071 431.641 661.071 406.777ZM727.737 406.777C727.737 457.286 694.219 499.913 648.248 513.79C727.063 528.912 786.666 598.136 786.666 681.333C786.666 699.742 771.742 714.666 753.333 714.666C734.924 714.666 720 699.742 720 681.333C720 623.977 673.414 577.389 615.833 577.389C558.253 577.389 511.666 623.977 511.666 681.333C511.666 699.742 496.742 714.666 478.333 714.666C459.924 714.666 445 699.742 445 681.333C445 598.136 504.603 528.912 583.417 513.79C537.446 499.913 503.929 457.286 503.929 406.777C503.929 344.993 554.081 295 615.833 295C677.584 295 727.737 344.993 727.737 406.777Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path d="M750.901 72L108 714.9" stroke="currentColor" strokeWidth="63" strokeLinecap="round" />
|
||||
</SvgIconWrapper>
|
||||
);
|
||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
||||
import { ContextStoreDataUpdate, useContextStore } from '@/hooks/Mapper/utils';
|
||||
import { createContext, Dispatch, ForwardedRef, forwardRef, SetStateAction, useContext, useEffect } from 'react';
|
||||
import { createContext, Dispatch, ForwardedRef, forwardRef, SetStateAction, useContext } from 'react';
|
||||
import {
|
||||
ActivitySummary,
|
||||
CommandLinkSignatureToSystem,
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
} from '@/hooks/Mapper/types';
|
||||
import { useCharactersCache, useComments, useMapRootHandlers } from '@/hooks/Mapper/mapRootProvider/hooks';
|
||||
import { WithChildren } from '@/hooks/Mapper/types/common.ts';
|
||||
import useLocalStorageState from 'use-local-storage-state';
|
||||
import {
|
||||
ToggleWidgetVisibility,
|
||||
useStoreWidgets,
|
||||
@@ -20,6 +19,9 @@ import {
|
||||
} from '@/hooks/Mapper/mapRootProvider/hooks/useStoreWidgets.ts';
|
||||
import { WindowsManagerOnChange } from '@/hooks/Mapper/components/ui-kit/WindowManager';
|
||||
import { DetailedKill } from '../types/kills';
|
||||
import { InterfaceStoredSettings, RoutesType } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
import { DEFAULT_ROUTES_SETTINGS, STORED_INTERFACE_DEFAULT_VALUES } from '@/hooks/Mapper/mapRootProvider/constants.ts';
|
||||
import { useMapUserSettings } from '@/hooks/Mapper/mapRootProvider/hooks/useMapUserSettings.ts';
|
||||
|
||||
export type MapRootData = MapUnionTypes & {
|
||||
selectedSystems: string[];
|
||||
@@ -50,7 +52,9 @@ const INITIAL_DATA: MapRootData = {
|
||||
systems: [],
|
||||
systemSignatures: {},
|
||||
hubs: [],
|
||||
userHubs: [],
|
||||
routes: undefined,
|
||||
userRoutes: undefined,
|
||||
kills: [],
|
||||
connections: [],
|
||||
detailedKills: {},
|
||||
@@ -64,11 +68,6 @@ const INITIAL_DATA: MapRootData = {
|
||||
followingCharacterEveId: null,
|
||||
};
|
||||
|
||||
export enum AvailableThemes {
|
||||
default = 'default',
|
||||
pathfinder = 'pathfinder',
|
||||
}
|
||||
|
||||
export enum InterfaceStoredSettingsProps {
|
||||
isShowMenu = 'isShowMenu',
|
||||
isShowMinimap = 'isShowMinimap',
|
||||
@@ -80,40 +79,28 @@ export enum InterfaceStoredSettingsProps {
|
||||
theme = 'theme',
|
||||
}
|
||||
|
||||
export type InterfaceStoredSettings = {
|
||||
isShowMenu: boolean;
|
||||
isShowMinimap: boolean;
|
||||
isShowKSpace: boolean;
|
||||
isThickConnections: boolean;
|
||||
isShowUnsplashedSignatures: boolean;
|
||||
isShowBackgroundPattern: boolean;
|
||||
isSoftBackground: boolean;
|
||||
theme: AvailableThemes;
|
||||
};
|
||||
|
||||
export const STORED_INTERFACE_DEFAULT_VALUES: InterfaceStoredSettings = {
|
||||
isShowMenu: false,
|
||||
isShowMinimap: true,
|
||||
isShowKSpace: false,
|
||||
isThickConnections: false,
|
||||
isShowUnsplashedSignatures: false,
|
||||
isShowBackgroundPattern: true,
|
||||
isSoftBackground: false,
|
||||
theme: AvailableThemes.default,
|
||||
};
|
||||
|
||||
export interface MapRootContextProps {
|
||||
update: ContextStoreDataUpdate<MapRootData>;
|
||||
data: MapRootData;
|
||||
outCommand: OutCommandHandler;
|
||||
interfaceSettings: InterfaceStoredSettings;
|
||||
setInterfaceSettings: Dispatch<SetStateAction<InterfaceStoredSettings>>;
|
||||
windowsSettings: WindowStoreInfo;
|
||||
toggleWidgetVisibility: ToggleWidgetVisibility;
|
||||
updateWidgetSettings: WindowsManagerOnChange;
|
||||
resetWidgets: () => void;
|
||||
comments: UseCommentsData;
|
||||
charactersCache: UseCharactersCacheData;
|
||||
|
||||
/**
|
||||
* !!!
|
||||
* DO NOT PASS THIS PROP INTO COMPONENT
|
||||
* !!!
|
||||
* */
|
||||
storedSettings: {
|
||||
interfaceSettings: InterfaceStoredSettings;
|
||||
setInterfaceSettings: Dispatch<SetStateAction<InterfaceStoredSettings>>;
|
||||
settingsRoutes: RoutesType;
|
||||
settingsRoutesUpdate: Dispatch<SetStateAction<RoutesType>>;
|
||||
};
|
||||
}
|
||||
|
||||
const MapRootContext = createContext<MapRootContextProps>({
|
||||
@@ -121,8 +108,6 @@ const MapRootContext = createContext<MapRootContextProps>({
|
||||
data: { ...INITIAL_DATA },
|
||||
// @ts-ignore
|
||||
outCommand: async () => void 0,
|
||||
interfaceSettings: STORED_INTERFACE_DEFAULT_VALUES,
|
||||
setInterfaceSettings: () => null,
|
||||
comments: {
|
||||
loadComments: async () => {},
|
||||
comments: new Map(),
|
||||
@@ -141,6 +126,12 @@ const MapRootContext = createContext<MapRootContextProps>({
|
||||
characters: new Map(),
|
||||
lastUpdateKey: 0,
|
||||
},
|
||||
storedSettings: {
|
||||
interfaceSettings: STORED_INTERFACE_DEFAULT_VALUES,
|
||||
setInterfaceSettings: () => null,
|
||||
settingsRoutes: DEFAULT_ROUTES_SETTINGS,
|
||||
settingsRoutesUpdate: () => null,
|
||||
},
|
||||
});
|
||||
|
||||
type MapRootProviderProps = {
|
||||
@@ -159,49 +150,25 @@ const MapRootHandlers = forwardRef(({ children }: WithChildren, fwdRef: Forwarde
|
||||
export const MapRootProvider = ({ children, fwdRef, outCommand }: MapRootProviderProps) => {
|
||||
const { update, ref } = useContextStore<MapRootData>({ ...INITIAL_DATA });
|
||||
|
||||
const [interfaceSettings, setInterfaceSettings] = useLocalStorageState<InterfaceStoredSettings>(
|
||||
'window:interface:settings',
|
||||
{
|
||||
defaultValue: STORED_INTERFACE_DEFAULT_VALUES,
|
||||
},
|
||||
);
|
||||
const storedSettings = useMapUserSettings();
|
||||
|
||||
const { windowsSettings, toggleWidgetVisibility, updateWidgetSettings, resetWidgets } = useStoreWidgets();
|
||||
const comments = useComments({ outCommand });
|
||||
const charactersCache = useCharactersCache({ outCommand });
|
||||
|
||||
useEffect(() => {
|
||||
let foundNew = false;
|
||||
const newVals = Object.keys(STORED_INTERFACE_DEFAULT_VALUES).reduce((acc, x) => {
|
||||
if (Object.keys(acc).includes(x)) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
foundNew = true;
|
||||
|
||||
// @ts-ignore
|
||||
return { ...acc, [x]: STORED_INTERFACE_DEFAULT_VALUES[x] };
|
||||
}, interfaceSettings);
|
||||
|
||||
if (foundNew) {
|
||||
setInterfaceSettings(newVals);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<MapRootContext.Provider
|
||||
value={{
|
||||
update,
|
||||
data: ref,
|
||||
outCommand,
|
||||
setInterfaceSettings,
|
||||
interfaceSettings,
|
||||
windowsSettings,
|
||||
updateWidgetSettings,
|
||||
toggleWidgetVisibility,
|
||||
resetWidgets,
|
||||
comments,
|
||||
charactersCache,
|
||||
storedSettings,
|
||||
}}
|
||||
>
|
||||
<MapRootHandlers ref={fwdRef}>{children}</MapRootHandlers>
|
||||
|
||||
26
assets/js/hooks/Mapper/mapRootProvider/constants.ts
Normal file
26
assets/js/hooks/Mapper/mapRootProvider/constants.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { AvailableThemes, InterfaceStoredSettings, RoutesType } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
|
||||
export const STORED_INTERFACE_DEFAULT_VALUES: InterfaceStoredSettings = {
|
||||
isShowMenu: false,
|
||||
isShowMinimap: true,
|
||||
isShowKSpace: false,
|
||||
isThickConnections: false,
|
||||
isShowUnsplashedSignatures: false,
|
||||
isShowBackgroundPattern: true,
|
||||
isSoftBackground: false,
|
||||
theme: AvailableThemes.default,
|
||||
};
|
||||
|
||||
export const DEFAULT_ROUTES_SETTINGS: RoutesType = {
|
||||
path_type: 'shortest',
|
||||
include_mass_crit: true,
|
||||
include_eol: true,
|
||||
include_frig: true,
|
||||
include_cruise: true,
|
||||
include_thera: true,
|
||||
avoid_wormholes: false,
|
||||
avoid_pochven: false,
|
||||
avoid_edencom: false,
|
||||
avoid_triglavian: false,
|
||||
avoid: [],
|
||||
};
|
||||
@@ -26,6 +26,7 @@ export const useMapInit = () => {
|
||||
is_subscription_active,
|
||||
main_character_eve_id,
|
||||
following_character_eve_id,
|
||||
user_hubs,
|
||||
} = props;
|
||||
|
||||
const updateData: Partial<MapRootData> = {};
|
||||
@@ -71,6 +72,10 @@ export const useMapInit = () => {
|
||||
updateData.hubs = hubs;
|
||||
}
|
||||
|
||||
if (user_hubs) {
|
||||
updateData.userHubs = user_hubs;
|
||||
}
|
||||
|
||||
if (options) {
|
||||
updateData.options = options;
|
||||
}
|
||||
|
||||
@@ -8,13 +8,33 @@ export const useMapUpdated = () => {
|
||||
const ref = useRef({ update });
|
||||
ref.current = { update };
|
||||
|
||||
return useCallback(({ hubs }: CommandMapUpdated) => {
|
||||
return useCallback((props: CommandMapUpdated) => {
|
||||
const { update } = ref.current;
|
||||
|
||||
const out: Partial<MapRootData> = {};
|
||||
|
||||
if (hubs) {
|
||||
out.hubs = hubs;
|
||||
if ('hubs' in props) {
|
||||
out.hubs = props.hubs;
|
||||
}
|
||||
|
||||
if ('user_hubs' in props) {
|
||||
out.userHubs = props.user_hubs;
|
||||
}
|
||||
|
||||
if ('system_signatures' in props) {
|
||||
out.systemSignatures = props.system_signatures;
|
||||
}
|
||||
|
||||
if ('following_character_eve_id' in props) {
|
||||
out.userCharacters = props.user_characters;
|
||||
}
|
||||
|
||||
if ('following_character_eve_id' in props) {
|
||||
out.followingCharacterEveId = props.following_character_eve_id;
|
||||
}
|
||||
|
||||
if ('main_character_eve_id' in props) {
|
||||
out.mainCharacterEveId = props.main_character_eve_id;
|
||||
}
|
||||
|
||||
update(out);
|
||||
|
||||
@@ -92,3 +92,23 @@ export const useRoutes = () => {
|
||||
update({ routes: value });
|
||||
}, []);
|
||||
};
|
||||
|
||||
export const useUserRoutes = () => {
|
||||
const {
|
||||
update,
|
||||
data: { userRoutes },
|
||||
} = useMapRootState();
|
||||
|
||||
const ref = useRef({ update, userRoutes });
|
||||
ref.current = { update, userRoutes };
|
||||
|
||||
return useCallback((value: CommandRoutes) => {
|
||||
const { update, userRoutes } = ref.current;
|
||||
|
||||
if (areRoutesListsEqual(userRoutes, value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
update({ userRoutes: value });
|
||||
}, []);
|
||||
};
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
useMapInit,
|
||||
useMapUpdated,
|
||||
useRoutes,
|
||||
useUserRoutes,
|
||||
} from './api';
|
||||
|
||||
import { useCommandsActivity } from './api/useCommandsActivity';
|
||||
@@ -54,6 +55,7 @@ export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
|
||||
useCommandsCharacters();
|
||||
const mapUpdated = useMapUpdated();
|
||||
const mapRoutes = useRoutes();
|
||||
const mapUserRoutes = useUserRoutes();
|
||||
const { addComment, removeComment } = useCommandComments();
|
||||
const { characterActivityData, trackingCharactersData, userSettingsUpdated } = useCommandsActivity();
|
||||
|
||||
@@ -105,6 +107,9 @@ export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
|
||||
case Commands.routes:
|
||||
mapRoutes(data as CommandRoutes);
|
||||
break;
|
||||
case Commands.userRoutes:
|
||||
mapUserRoutes(data as CommandRoutes);
|
||||
break;
|
||||
|
||||
case Commands.signaturesUpdated: // USED
|
||||
updateSystemSignatures(data as CommandSignaturesUpdated);
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import useLocalStorageState from 'use-local-storage-state';
|
||||
import { InterfaceStoredSettings, RoutesType } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
import { DEFAULT_ROUTES_SETTINGS, STORED_INTERFACE_DEFAULT_VALUES } from '@/hooks/Mapper/mapRootProvider/constants.ts';
|
||||
import { useActualizeSettings } from '@/hooks/Mapper/hooks';
|
||||
import { useEffect } from 'react';
|
||||
import { SESSION_KEY } from '@/hooks/Mapper/constants.ts';
|
||||
|
||||
export const useMigrationRoutesSettingsV1 = (update: (upd: RoutesType) => void) => {
|
||||
//TODO if current Date is more than 01.01.2026 - remove this hook.
|
||||
|
||||
useEffect(() => {
|
||||
const items = localStorage.getItem(SESSION_KEY.routes);
|
||||
if (items) {
|
||||
update(JSON.parse(items));
|
||||
localStorage.removeItem(SESSION_KEY.routes);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
};
|
||||
|
||||
export const useMapUserSettings = () => {
|
||||
const [interfaceSettings, setInterfaceSettings] = useLocalStorageState<InterfaceStoredSettings>(
|
||||
'window:interface:settings',
|
||||
{
|
||||
defaultValue: STORED_INTERFACE_DEFAULT_VALUES,
|
||||
},
|
||||
);
|
||||
|
||||
const [settingsRoutes, settingsRoutesUpdate] = useLocalStorageState<RoutesType>('window:interface:routes', {
|
||||
defaultValue: DEFAULT_ROUTES_SETTINGS,
|
||||
});
|
||||
|
||||
useActualizeSettings(STORED_INTERFACE_DEFAULT_VALUES, interfaceSettings, setInterfaceSettings);
|
||||
useActualizeSettings(DEFAULT_ROUTES_SETTINGS, settingsRoutes, settingsRoutesUpdate);
|
||||
|
||||
useMigrationRoutesSettingsV1(settingsRoutesUpdate);
|
||||
|
||||
return { interfaceSettings, setInterfaceSettings, settingsRoutes, settingsRoutesUpdate };
|
||||
};
|
||||
29
assets/js/hooks/Mapper/mapRootProvider/types.ts
Normal file
29
assets/js/hooks/Mapper/mapRootProvider/types.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
export enum AvailableThemes {
|
||||
default = 'default',
|
||||
pathfinder = 'pathfinder',
|
||||
}
|
||||
|
||||
export type InterfaceStoredSettings = {
|
||||
isShowMenu: boolean;
|
||||
isShowMinimap: boolean;
|
||||
isShowKSpace: boolean;
|
||||
isThickConnections: boolean;
|
||||
isShowUnsplashedSignatures: boolean;
|
||||
isShowBackgroundPattern: boolean;
|
||||
isSoftBackground: boolean;
|
||||
theme: AvailableThemes;
|
||||
};
|
||||
|
||||
export type RoutesType = {
|
||||
path_type: 'shortest' | 'secure' | 'insecure';
|
||||
include_mass_crit: boolean;
|
||||
include_eol: boolean;
|
||||
include_frig: boolean;
|
||||
include_cruise: boolean;
|
||||
include_thera: boolean;
|
||||
avoid_wormholes: boolean;
|
||||
avoid_pochven: boolean;
|
||||
avoid_edencom: boolean;
|
||||
avoid_triglavian: boolean;
|
||||
avoid: number[];
|
||||
};
|
||||
@@ -28,8 +28,8 @@ export type CharacterTypeRaw = {
|
||||
ship: ShipTypeRaw | null;
|
||||
|
||||
alliance_id: number | null;
|
||||
alliance_name: number | null;
|
||||
alliance_ticker: number | null;
|
||||
alliance_name: string | null;
|
||||
alliance_ticker: string | null;
|
||||
corporation_id: number;
|
||||
corporation_name: string;
|
||||
corporation_ticker: string;
|
||||
|
||||
@@ -24,6 +24,7 @@ export enum Commands {
|
||||
killsUpdated = 'kills_updated',
|
||||
detailedKillsUpdated = 'detailed_kills_updated',
|
||||
routes = 'routes',
|
||||
userRoutes = 'user_routes',
|
||||
centerSystem = 'center_system',
|
||||
selectSystem = 'select_system',
|
||||
linkSignatureToSystem = 'link_signature_to_system',
|
||||
@@ -55,6 +56,7 @@ export type Command =
|
||||
| Commands.killsUpdated
|
||||
| Commands.detailedKillsUpdated
|
||||
| Commands.routes
|
||||
| Commands.userRoutes
|
||||
| Commands.selectSystem
|
||||
| Commands.centerSystem
|
||||
| Commands.linkSignatureToSystem
|
||||
@@ -82,6 +84,7 @@ export type CommandInit = {
|
||||
user_characters: string[];
|
||||
user_permissions: UserPermissions;
|
||||
hubs: string[];
|
||||
user_hubs: string[];
|
||||
routes: RoutesList;
|
||||
options: Record<string, string | boolean>;
|
||||
reset?: boolean;
|
||||
@@ -104,6 +107,7 @@ export type CommandUpdateConnection = SolarSystemConnection;
|
||||
export type CommandSignaturesUpdated = string;
|
||||
export type CommandMapUpdated = Partial<CommandInit>;
|
||||
export type CommandRoutes = RoutesList;
|
||||
export type CommandUserRoutes = RoutesList;
|
||||
export type CommandKillsUpdated = Kill[];
|
||||
export type CommandDetailedKillsUpdated = Record<string, DetailedKill[]>;
|
||||
export type CommandSelectSystem = string | undefined;
|
||||
@@ -170,6 +174,7 @@ export interface CommandData {
|
||||
[Commands.updateConnection]: CommandUpdateConnection;
|
||||
[Commands.mapUpdated]: CommandMapUpdated;
|
||||
[Commands.routes]: CommandRoutes;
|
||||
[Commands.userRoutes]: CommandUserRoutes;
|
||||
[Commands.killsUpdated]: CommandKillsUpdated;
|
||||
[Commands.detailedKillsUpdated]: CommandDetailedKillsUpdated;
|
||||
[Commands.selectSystem]: CommandSelectSystem;
|
||||
@@ -194,7 +199,10 @@ export interface MapHandlers {
|
||||
export enum OutCommand {
|
||||
addHub = 'add_hub',
|
||||
deleteHub = 'delete_hub',
|
||||
addUserHub = 'add_user_hub',
|
||||
deleteUserHub = 'delete_user_hub',
|
||||
getRoutes = 'get_routes',
|
||||
getUserRoutes = 'get_user_routes',
|
||||
getCharacterJumps = 'get_character_jumps',
|
||||
getStructures = 'get_structures',
|
||||
getSignatures = 'get_signatures',
|
||||
|
||||
@@ -15,9 +15,11 @@ export type MapUnionTypes = {
|
||||
userCharacters: string[];
|
||||
presentCharacters: string[];
|
||||
hubs: string[];
|
||||
userHubs: string[];
|
||||
systems: SolarSystemRawType[];
|
||||
systemSignatures: Record<string, SystemSignature[]>;
|
||||
routes?: RoutesList;
|
||||
userRoutes?: RoutesList;
|
||||
kills: Record<number, number>;
|
||||
connections: SolarSystemConnection[];
|
||||
userPermissions: Partial<UserPermissions>;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { MapHandlers } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { RefObject, useCallback } from 'react';
|
||||
|
||||
// Force reload the page after 30 minutes of inactivity
|
||||
const FORCE_PAGE_RELOAD_TIMEOUT = 1000 * 60 * 30;
|
||||
// Force reload the page after 5 minutes of inactivity
|
||||
const FORCE_PAGE_RELOAD_TIMEOUT = 1000 * 60 * 5;
|
||||
|
||||
export const useMapperHandlers = (handlerRefs: RefObject<MapHandlers>[], hooksRef: RefObject<any>) => {
|
||||
const handleCommand = useCallback(
|
||||
|
||||
@@ -21,6 +21,8 @@ defmodule WandererApp.Api.MapUserSettings do
|
||||
define(:update_settings, action: :update_settings)
|
||||
define(:update_main_character, action: :update_main_character)
|
||||
define(:update_following_character, action: :update_following_character)
|
||||
|
||||
define(:update_hubs, action: :update_hubs)
|
||||
end
|
||||
|
||||
actions do
|
||||
@@ -43,6 +45,10 @@ defmodule WandererApp.Api.MapUserSettings do
|
||||
update :update_following_character do
|
||||
accept [:following_character_eve_id]
|
||||
end
|
||||
|
||||
update :update_hubs do
|
||||
accept [:hubs]
|
||||
end
|
||||
end
|
||||
|
||||
attributes do
|
||||
@@ -59,6 +65,12 @@ defmodule WandererApp.Api.MapUserSettings do
|
||||
attribute :following_character_eve_id, :string do
|
||||
allow_nil? true
|
||||
end
|
||||
|
||||
attribute :hubs, {:array, :string} do
|
||||
allow_nil?(true)
|
||||
|
||||
default([])
|
||||
end
|
||||
end
|
||||
|
||||
relationships do
|
||||
|
||||
@@ -68,15 +68,22 @@ defmodule WandererApp.Character.TrackingUtils do
|
||||
{:ok, main_character} =
|
||||
get_main_character(user_settings, characters_with_access, characters_with_access)
|
||||
|
||||
following_character_eve_id = case user_settings do
|
||||
nil -> nil
|
||||
%{following_character_eve_id: following_character_eve_id} -> following_character_eve_id
|
||||
end
|
||||
following_character_eve_id =
|
||||
case user_settings do
|
||||
nil -> nil
|
||||
%{following_character_eve_id: following_character_eve_id} -> following_character_eve_id
|
||||
end
|
||||
|
||||
main_character_eve_id =
|
||||
case main_character do
|
||||
nil -> nil
|
||||
%{eve_id: eve_id} -> eve_id
|
||||
end
|
||||
|
||||
{:ok,
|
||||
%{
|
||||
characters: characters_data,
|
||||
main: main_character.eve_id,
|
||||
main: main_character_eve_id,
|
||||
following: following_character_eve_id
|
||||
}}
|
||||
else
|
||||
|
||||
@@ -72,6 +72,9 @@ defmodule WandererApp.Map do
|
||||
def get_characters_limit(map_id),
|
||||
do: {:ok, map_id |> get_map!() |> Map.get(:characters_limit, 50)}
|
||||
|
||||
def get_hubs_limit(map_id),
|
||||
do: {:ok, map_id |> get_map!() |> Map.get(:hubs_limit, 20)}
|
||||
|
||||
def is_subscription_active?(map_id),
|
||||
do: is_subscription_active?(map_id, WandererApp.Env.map_subscriptions_enabled?())
|
||||
|
||||
@@ -105,10 +108,14 @@ defmodule WandererApp.Map do
|
||||
|
||||
def list_hubs(map_id) do
|
||||
{:ok, map} = map_id |> get_map()
|
||||
hubs = map |> Map.get(:hubs, [])
|
||||
hubs_limit = map |> Map.get(:hubs_limit, 20)
|
||||
|
||||
{:ok, hubs |> _maybe_limit_list(hubs_limit)}
|
||||
{:ok, map |> Map.get(:hubs, [])}
|
||||
end
|
||||
|
||||
def list_hubs(map_id, hubs) do
|
||||
{:ok, map} = map_id |> get_map()
|
||||
|
||||
{:ok, hubs}
|
||||
end
|
||||
|
||||
def list_connections(map_id),
|
||||
@@ -148,15 +155,16 @@ defmodule WandererApp.Map do
|
||||
|
||||
case not (characters |> Enum.member?(character_id)) do
|
||||
true ->
|
||||
{:ok, %{
|
||||
alliance_id: alliance_id,
|
||||
corporation_id: corporation_id,
|
||||
solar_system_id: solar_system_id,
|
||||
structure_id: structure_id,
|
||||
station_id: station_id,
|
||||
ship: ship_type_id,
|
||||
ship_name: ship_name
|
||||
}} = WandererApp.Character.get_character(character_id)
|
||||
{:ok,
|
||||
%{
|
||||
alliance_id: alliance_id,
|
||||
corporation_id: corporation_id,
|
||||
solar_system_id: solar_system_id,
|
||||
structure_id: structure_id,
|
||||
station_id: station_id,
|
||||
ship: ship_type_id,
|
||||
ship_name: ship_name
|
||||
}} = WandererApp.Character.get_character(character_id)
|
||||
|
||||
map_id
|
||||
|> update_map(%{characters: [character_id | characters]})
|
||||
@@ -536,9 +544,6 @@ defmodule WandererApp.Map do
|
||||
end
|
||||
end
|
||||
|
||||
defp _maybe_limit_list(list, nil), do: list
|
||||
defp _maybe_limit_list(list, limit), do: Enum.take(list, limit)
|
||||
|
||||
@doc """
|
||||
Returns the raw activity data that can be processed by WandererApp.Character.Activity.
|
||||
Only includes characters that are on the map's ACL.
|
||||
@@ -549,7 +554,8 @@ defmodule WandererApp.Map do
|
||||
_map_with_acls = Ash.load!(map, :acls)
|
||||
|
||||
# Calculate cutoff date if days is provided
|
||||
cutoff_date = if days, do: DateTime.utc_now() |> DateTime.add(-days * 24 * 3600, :second), else: nil
|
||||
cutoff_date =
|
||||
if days, do: DateTime.utc_now() |> DateTime.add(-days * 24 * 3600, :second), else: nil
|
||||
|
||||
# Get activity data
|
||||
passages_activity = get_passages_activity(map_id, cutoff_date)
|
||||
|
||||
@@ -119,6 +119,12 @@ defmodule WandererApp.Map.Server.SignaturesImpl do
|
||||
end
|
||||
end
|
||||
|
||||
def update_signatures(
|
||||
state,
|
||||
_signatures_update
|
||||
),
|
||||
do: state
|
||||
|
||||
defp parse_signatures(signatures, character_eve_id, system_id),
|
||||
do:
|
||||
signatures
|
||||
|
||||
@@ -266,31 +266,32 @@ defmodule WandererApp.Map.Server.SystemsImpl do
|
||||
|> Enum.filter(fn system -> not is_nil(system) && not system.locked end)
|
||||
|> Enum.map(&{&1.solar_system_id, &1.id})
|
||||
|
||||
solar_system_ids_to_remove =
|
||||
filtered_ids
|
||||
|> Enum.map(fn {solar_system_id, _} -> solar_system_id end)
|
||||
|
||||
system_ids_to_remove =
|
||||
filtered_ids
|
||||
|> Enum.map(fn {_, system_id} -> system_id end)
|
||||
|
||||
connections_to_remove =
|
||||
solar_system_ids_to_remove
|
||||
|> Enum.map(fn solar_system_id ->
|
||||
WandererApp.Map.find_connections(map_id, solar_system_id)
|
||||
end)
|
||||
|> List.flatten()
|
||||
|> Enum.uniq_by(& &1.id)
|
||||
|
||||
:ok = WandererApp.Map.remove_connections(map_id, connections_to_remove)
|
||||
:ok = WandererApp.Map.remove_systems(map_id, solar_system_ids_to_remove)
|
||||
|
||||
solar_system_ids_to_remove
|
||||
|> Enum.each(fn solar_system_id ->
|
||||
filtered_ids
|
||||
|> Enum.each(fn {solar_system_id, system_id} ->
|
||||
map_id
|
||||
|> WandererApp.MapSystemRepo.remove_from_map(solar_system_id)
|
||||
|> case do
|
||||
{:ok, _} ->
|
||||
:ok = WandererApp.Map.remove_system(map_id, solar_system_id)
|
||||
@ddrt.delete([solar_system_id], rtree_name)
|
||||
Impl.broadcast!(map_id, :systems_removed, [solar_system_id])
|
||||
track_systems_removed(map_id, user_id, character_id, [solar_system_id])
|
||||
remove_system_connections(map_id, [solar_system_id])
|
||||
|
||||
try do
|
||||
cleanup_linked_signatures(map_id, [solar_system_id])
|
||||
rescue
|
||||
e ->
|
||||
Logger.error("Failed to cleanup linked signature: #{inspect(e)}")
|
||||
end
|
||||
|
||||
try do
|
||||
cleanup_linked_system_sig_eve_ids(state, [system_id])
|
||||
rescue
|
||||
e ->
|
||||
Logger.error("Failed to cleanup system linked sig eve ids: #{inspect(e)}")
|
||||
end
|
||||
|
||||
:ok
|
||||
|
||||
{:error, error} ->
|
||||
@@ -299,25 +300,68 @@ defmodule WandererApp.Map.Server.SystemsImpl do
|
||||
end
|
||||
end)
|
||||
|
||||
state
|
||||
end
|
||||
|
||||
defp track_systems_removed(map_id, user_id, character_id, removed_solar_system_ids)
|
||||
when not is_nil(user_id) and not is_nil(character_id) do
|
||||
WandererApp.User.ActivityTracker.track_map_event(:systems_removed, %{
|
||||
character_id: character_id,
|
||||
user_id: user_id,
|
||||
map_id: map_id,
|
||||
solar_system_ids: removed_solar_system_ids
|
||||
})
|
||||
|> case do
|
||||
{:ok, _} -> :ok
|
||||
error -> Logger.error("Failed to track systems removed: #{inspect(error)}")
|
||||
end
|
||||
end
|
||||
|
||||
defp track_systems_removed(_map_id, _user_id, _character_id, _removed_solar_system_ids), do: :ok
|
||||
|
||||
defp remove_system_connections(map_id, solar_system_ids_to_remove) do
|
||||
connections_to_remove =
|
||||
solar_system_ids_to_remove
|
||||
|> Enum.map(fn solar_system_id ->
|
||||
WandererApp.Map.find_connections(map_id, solar_system_id)
|
||||
end)
|
||||
|> List.flatten()
|
||||
|> Enum.uniq_by(& &1.id)
|
||||
|
||||
connections_to_remove
|
||||
|> Enum.each(fn connection ->
|
||||
Logger.debug(fn -> "Removing connection from map: #{inspect(connection)}" end)
|
||||
WandererApp.MapConnectionRepo.destroy(map_id, connection)
|
||||
try do
|
||||
Logger.debug(fn -> "Removing connection from map: #{inspect(connection)}" end)
|
||||
:ok = WandererApp.MapConnectionRepo.destroy(map_id, connection)
|
||||
:ok = WandererApp.Map.remove_connection(map_id, connection)
|
||||
Impl.broadcast!(map_id, :remove_connections, [connection])
|
||||
rescue
|
||||
e ->
|
||||
Logger.error("Failed to remove connection: #{inspect(e)}")
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
solar_system_ids_to_remove
|
||||
defp cleanup_linked_signatures(map_id, removed_solar_system_ids) do
|
||||
removed_solar_system_ids
|
||||
|> Enum.map(fn solar_system_id ->
|
||||
WandererApp.Api.MapSystemSignature.by_linked_system_id!(solar_system_id)
|
||||
end)
|
||||
|> List.flatten()
|
||||
|> Enum.uniq_by(& &1.system_id)
|
||||
|> Enum.each(fn s ->
|
||||
{:ok, %{system: system}} = s |> Ash.load([:system])
|
||||
Ash.destroy!(s)
|
||||
|
||||
Impl.broadcast!(map_id, :signatures_updated, system.solar_system_id)
|
||||
try do
|
||||
{:ok, %{system: system}} = s |> Ash.load([:system])
|
||||
:ok = Ash.destroy!(s)
|
||||
Impl.broadcast!(map_id, :signatures_updated, system.solar_system_id)
|
||||
rescue
|
||||
e ->
|
||||
Logger.error("Failed to cleanup linked signature: #{inspect(e)}")
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp cleanup_linked_system_sig_eve_ids(state, system_ids_to_remove) do
|
||||
linked_system_ids =
|
||||
system_ids_to_remove
|
||||
|> Enum.map(fn system_id ->
|
||||
@@ -335,34 +379,6 @@ defmodule WandererApp.Map.Server.SystemsImpl do
|
||||
linked_sig_eve_id: nil
|
||||
})
|
||||
end)
|
||||
|
||||
@ddrt.delete(solar_system_ids_to_remove, rtree_name)
|
||||
|
||||
Impl.broadcast!(map_id, :remove_connections, connections_to_remove)
|
||||
Impl.broadcast!(map_id, :systems_removed, solar_system_ids_to_remove)
|
||||
|
||||
case not is_nil(user_id) do
|
||||
true ->
|
||||
{:ok, _} =
|
||||
WandererApp.User.ActivityTracker.track_map_event(:systems_removed, %{
|
||||
character_id: character_id,
|
||||
user_id: user_id,
|
||||
map_id: map_id,
|
||||
solar_system_ids: solar_system_ids_to_remove
|
||||
})
|
||||
|
||||
:telemetry.execute(
|
||||
[:wanderer_app, :map, :systems, :remove],
|
||||
%{count: solar_system_ids_to_remove |> Enum.count()}
|
||||
)
|
||||
|
||||
:ok
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
|
||||
state
|
||||
end
|
||||
|
||||
def maybe_add_system(map_id, location, old_location, rtree_name, map_opts)
|
||||
|
||||
@@ -48,6 +48,33 @@ defmodule WandererApp.MapUserSettingsRepo do
|
||||
end
|
||||
end
|
||||
|
||||
def get_hubs(map_id, user_id) do
|
||||
case WandererApp.MapUserSettingsRepo.get(map_id, user_id) do
|
||||
{:ok, user_settings} when not is_nil(user_settings) ->
|
||||
{:ok, Map.get(user_settings, :hubs, [])}
|
||||
|
||||
_ ->
|
||||
{:ok, []}
|
||||
end
|
||||
end
|
||||
|
||||
def update_hubs(map_id, user_id, hubs) do
|
||||
get!(map_id, user_id)
|
||||
|> case do
|
||||
user_settings when not is_nil(user_settings) ->
|
||||
user_settings
|
||||
|> WandererApp.Api.MapUserSettings.update_hubs(%{hubs: hubs})
|
||||
|
||||
_ ->
|
||||
WandererApp.Api.MapUserSettings.create!(%{
|
||||
map_id: map_id,
|
||||
user_id: user_id,
|
||||
settings: @default_form_data |> Jason.encode!()
|
||||
})
|
||||
|> WandererApp.Api.MapUserSettings.update_hubs(%{hubs: hubs})
|
||||
end
|
||||
end
|
||||
|
||||
def to_form_data(nil), do: {:ok, @default_form_data}
|
||||
def to_form_data(%{settings: settings} = _user_settings), do: {:ok, Jason.decode!(settings)}
|
||||
|
||||
|
||||
@@ -83,12 +83,11 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
|
||||
socket
|
||||
|> assign(has_tracked_characters?: user_character_eve_ids |> Enum.empty?() |> Kernel.not())
|
||||
|> MapEventHandler.push_map_event(
|
||||
"init",
|
||||
"map_updated",
|
||||
%{
|
||||
main_character_eve_id: main_character_eve_id,
|
||||
following_character_eve_id: following_character_eve_id,
|
||||
user_characters: user_character_eve_ids,
|
||||
reset: false
|
||||
user_characters: user_character_eve_ids
|
||||
}
|
||||
)
|
||||
end
|
||||
@@ -183,31 +182,32 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
|
||||
} = socket
|
||||
)
|
||||
when character_eve_id != following_character_eve_id do
|
||||
settings = case map_user_settings do
|
||||
nil -> nil
|
||||
%{settings: settings} -> settings
|
||||
end
|
||||
settings =
|
||||
case map_user_settings do
|
||||
nil -> nil
|
||||
%{settings: settings} -> settings
|
||||
end
|
||||
|
||||
{:ok, user_settings} =
|
||||
WandererApp.MapUserSettingsRepo.create_or_update(map_id, current_user_id, settings)
|
||||
{:ok, user_settings} =
|
||||
WandererApp.MapUserSettingsRepo.create_or_update(map_id, current_user_id, settings)
|
||||
|
||||
{:ok, map_user_settings} =
|
||||
user_settings
|
||||
|> WandererApp.Api.MapUserSettings.update_following_character(%{
|
||||
following_character_eve_id: "#{character_eve_id}"
|
||||
})
|
||||
{:ok, map_user_settings} =
|
||||
user_settings
|
||||
|> WandererApp.Api.MapUserSettings.update_following_character(%{
|
||||
following_character_eve_id: "#{character_eve_id}"
|
||||
})
|
||||
|
||||
{:ok, tracking_data} =
|
||||
WandererApp.Character.TrackingUtils.build_tracking_data(map_id, current_user_id)
|
||||
{:ok, tracking_data} =
|
||||
WandererApp.Character.TrackingUtils.build_tracking_data(map_id, current_user_id)
|
||||
|
||||
Process.send_after(self(), %{event: :refresh_user_characters}, 50)
|
||||
Process.send_after(self(), %{event: :refresh_user_characters}, 50)
|
||||
|
||||
{:reply, %{data: tracking_data},
|
||||
socket
|
||||
|> assign(
|
||||
map_user_settings: map_user_settings,
|
||||
following_character_eve_id: "#{character_eve_id}"
|
||||
)}
|
||||
{:reply, %{data: tracking_data},
|
||||
socket
|
||||
|> assign(
|
||||
map_user_settings: map_user_settings,
|
||||
following_character_eve_id: "#{character_eve_id}"
|
||||
)}
|
||||
end
|
||||
|
||||
def handle_ui_event(
|
||||
@@ -223,46 +223,47 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
|
||||
} = socket
|
||||
)
|
||||
when not is_nil(character_eve_id) and character_eve_id != main_character_eve_id do
|
||||
settings = case map_user_settings do
|
||||
nil -> nil
|
||||
%{settings: settings} -> settings
|
||||
end
|
||||
settings =
|
||||
case map_user_settings do
|
||||
nil -> nil
|
||||
%{settings: settings} -> settings
|
||||
end
|
||||
|
||||
{:ok, user_settings} =
|
||||
WandererApp.MapUserSettingsRepo.create_or_update(map_id, current_user_id, settings)
|
||||
{:ok, user_settings} =
|
||||
WandererApp.MapUserSettingsRepo.create_or_update(map_id, current_user_id, settings)
|
||||
|
||||
{:ok, map_user_settings} =
|
||||
user_settings
|
||||
|> WandererApp.Api.MapUserSettings.update_main_character(%{
|
||||
main_character_eve_id: "#{character_eve_id}"
|
||||
})
|
||||
{:ok, map_user_settings} =
|
||||
user_settings
|
||||
|> WandererApp.Api.MapUserSettings.update_main_character(%{
|
||||
main_character_eve_id: "#{character_eve_id}"
|
||||
})
|
||||
|
||||
{:ok, tracking_data} =
|
||||
WandererApp.Character.TrackingUtils.build_tracking_data(map_id, current_user_id)
|
||||
{:ok, tracking_data} =
|
||||
WandererApp.Character.TrackingUtils.build_tracking_data(map_id, current_user_id)
|
||||
|
||||
{main_character_id, main_character_eve_id} =
|
||||
WandererApp.Character.TrackingUtils.get_main_character(
|
||||
map_user_settings,
|
||||
current_user_characters,
|
||||
current_user_characters
|
||||
)
|
||||
|> case do
|
||||
{:ok, main_character} when not is_nil(main_character) ->
|
||||
{main_character.id, main_character.eve_id}
|
||||
{main_character_id, main_character_eve_id} =
|
||||
WandererApp.Character.TrackingUtils.get_main_character(
|
||||
map_user_settings,
|
||||
current_user_characters,
|
||||
current_user_characters
|
||||
)
|
||||
|> case do
|
||||
{:ok, main_character} when not is_nil(main_character) ->
|
||||
{main_character.id, main_character.eve_id}
|
||||
|
||||
_ ->
|
||||
{nil, nil}
|
||||
end
|
||||
_ ->
|
||||
{nil, nil}
|
||||
end
|
||||
|
||||
Process.send_after(self(), %{event: :refresh_user_characters}, 50)
|
||||
Process.send_after(self(), %{event: :refresh_user_characters}, 50)
|
||||
|
||||
{:reply, %{data: tracking_data},
|
||||
socket
|
||||
|> assign(
|
||||
map_user_settings: map_user_settings,
|
||||
main_character_id: main_character_id,
|
||||
main_character_eve_id: main_character_eve_id
|
||||
)}
|
||||
{:reply, %{data: tracking_data},
|
||||
socket
|
||||
|> assign(
|
||||
map_user_settings: map_user_settings,
|
||||
main_character_id: main_character_id,
|
||||
main_character_eve_id: main_character_eve_id
|
||||
)}
|
||||
end
|
||||
|
||||
def handle_ui_event(event, body, socket),
|
||||
|
||||
@@ -229,11 +229,6 @@ defmodule WandererAppWeb.MapCoreEventHandler do
|
||||
|
||||
def handle_ui_event("noop", _, socket), do: {:noreply, socket}
|
||||
|
||||
def handle_ui_event(event, body, socket) do
|
||||
Logger.debug(fn -> "unhandled map ui event: #{inspect(event)} #{inspect(body)}" end)
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_ui_event(
|
||||
_event,
|
||||
_body,
|
||||
@@ -250,6 +245,23 @@ defmodule WandererAppWeb.MapCoreEventHandler do
|
||||
)}
|
||||
end
|
||||
|
||||
def handle_ui_event(
|
||||
event,
|
||||
body,
|
||||
%{assigns: %{main_character_id: main_character_id}} =
|
||||
socket
|
||||
)
|
||||
when is_nil(main_character_id) do
|
||||
Process.send_after(self(), :no_main_character_set, 100)
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_ui_event(event, body, socket) do
|
||||
Logger.debug(fn -> "unhandled map ui event: #{inspect(event)} #{inspect(body)}" end)
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
defp maybe_start_map(map_id) do
|
||||
{:ok, map_server_started} = WandererApp.Cache.lookup("map_#{map_id}:started", false)
|
||||
|
||||
@@ -505,6 +517,7 @@ defmodule WandererAppWeb.MapCoreEventHandler do
|
||||
defp map_start(
|
||||
%{
|
||||
assigns: %{
|
||||
current_user: current_user,
|
||||
needs_tracking_setup: needs_tracking_setup,
|
||||
main_character_id: main_character_id,
|
||||
main_character_eve_id: main_character_eve_id,
|
||||
@@ -536,7 +549,7 @@ defmodule WandererAppWeb.MapCoreEventHandler do
|
||||
|
||||
map_data =
|
||||
map_id
|
||||
|> get_map_data()
|
||||
|> get_map_data(current_user.id, is_subscription_active)
|
||||
|
||||
socket =
|
||||
socket
|
||||
@@ -579,11 +592,19 @@ defmodule WandererAppWeb.MapCoreEventHandler do
|
||||
end
|
||||
end
|
||||
|
||||
defp get_map_data(map_id) do
|
||||
defp get_map_data(map_id, current_user_id, is_subscription_active) do
|
||||
{:ok, hubs} = map_id |> WandererApp.Map.list_hubs()
|
||||
{:ok, hubs_limit} = map_id |> WandererApp.Map.get_hubs_limit()
|
||||
{:ok, connections} = map_id |> WandererApp.Map.list_connections()
|
||||
{:ok, systems} = map_id |> WandererApp.Map.list_systems()
|
||||
|
||||
{:ok, user_hubs} =
|
||||
if is_subscription_active do
|
||||
WandererApp.MapUserSettingsRepo.get_hubs(map_id, current_user_id)
|
||||
else
|
||||
{:ok, []}
|
||||
end
|
||||
|
||||
system_static_infos =
|
||||
systems
|
||||
|> Enum.map(&WandererApp.CachedInfo.get_system_static_info!(&1.solar_system_id))
|
||||
@@ -595,6 +616,8 @@ defmodule WandererAppWeb.MapCoreEventHandler do
|
||||
system_static_infos:
|
||||
system_static_infos |> Enum.map(&MapEventHandler.map_ui_system_static_info/1),
|
||||
hubs: hubs,
|
||||
hubs_limit: hubs_limit,
|
||||
user_hubs: user_hubs,
|
||||
connections: connections |> Enum.map(&MapEventHandler.map_ui_connection/1)
|
||||
}
|
||||
end
|
||||
|
||||
@@ -22,13 +22,12 @@ defmodule WandererAppWeb.MapKillsEventHandler do
|
||||
|
||||
socket
|
||||
|> MapEventHandler.push_map_event(
|
||||
"init",
|
||||
"map_updated",
|
||||
%{
|
||||
kills:
|
||||
kills
|
||||
|> Enum.filter(fn {_, kills} -> kills > 0 end)
|
||||
|> Enum.map(&map_ui_kill/1),
|
||||
reset: false
|
||||
|> Enum.map(&map_ui_kill/1)
|
||||
}
|
||||
)
|
||||
end
|
||||
@@ -45,11 +44,14 @@ defmodule WandererAppWeb.MapKillsEventHandler do
|
||||
)
|
||||
end
|
||||
|
||||
def handle_server_event(%{event: :detailed_kills_updated, payload: payload}, %{
|
||||
assigns: %{
|
||||
map_id: map_id
|
||||
}
|
||||
} = socket) do
|
||||
def handle_server_event(
|
||||
%{event: :detailed_kills_updated, payload: payload},
|
||||
%{
|
||||
assigns: %{
|
||||
map_id: map_id
|
||||
}
|
||||
} = socket
|
||||
) do
|
||||
case WandererApp.Map.is_subscription_active?(map_id) do
|
||||
{:ok, true} ->
|
||||
socket
|
||||
@@ -57,7 +59,9 @@ defmodule WandererAppWeb.MapKillsEventHandler do
|
||||
"detailed_kills_updated",
|
||||
payload
|
||||
)
|
||||
_ -> socket
|
||||
|
||||
_ ->
|
||||
socket
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ defmodule WandererAppWeb.MapRoutesEventHandler do
|
||||
use Phoenix.Component
|
||||
require Logger
|
||||
|
||||
alias WandererAppWeb.{MapEventHandler, MapCoreEventHandler}
|
||||
alias WandererAppWeb.{MapEventHandler, MapCoreEventHandler, MapSystemsEventHandler}
|
||||
|
||||
def handle_server_event(
|
||||
%{
|
||||
@@ -24,6 +24,25 @@ defmodule WandererAppWeb.MapRoutesEventHandler do
|
||||
}
|
||||
)
|
||||
|
||||
def handle_server_event(
|
||||
%{
|
||||
event: :user_routes,
|
||||
payload: {solar_system_id, %{routes: routes, systems_static_data: systems_static_data}}
|
||||
},
|
||||
socket
|
||||
),
|
||||
do:
|
||||
socket
|
||||
|> MapEventHandler.push_map_event(
|
||||
"user_routes",
|
||||
%{
|
||||
solar_system_id: solar_system_id,
|
||||
loading: false,
|
||||
routes: routes,
|
||||
systems_static_data: systems_static_data
|
||||
}
|
||||
)
|
||||
|
||||
def handle_server_event(event, socket),
|
||||
do: MapCoreEventHandler.handle_server_event(event, socket)
|
||||
|
||||
@@ -49,6 +68,39 @@ defmodule WandererAppWeb.MapRoutesEventHandler do
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_ui_event(
|
||||
"get_user_routes",
|
||||
%{"system_id" => solar_system_id, "routes_settings" => routes_settings} = _event,
|
||||
%{
|
||||
assigns: %{
|
||||
map_id: map_id,
|
||||
map_loaded?: true,
|
||||
current_user: current_user,
|
||||
is_subscription_active?: is_subscription_active?
|
||||
}
|
||||
} = socket
|
||||
) do
|
||||
Task.async(fn ->
|
||||
if is_subscription_active? do
|
||||
{:ok, hubs} = WandererApp.MapUserSettingsRepo.get_hubs(map_id, current_user.id)
|
||||
|
||||
{:ok, routes} =
|
||||
WandererApp.Maps.find_routes(
|
||||
map_id,
|
||||
hubs,
|
||||
solar_system_id,
|
||||
get_routes_settings(routes_settings)
|
||||
)
|
||||
|
||||
{:user_routes, {solar_system_id, routes}}
|
||||
else
|
||||
{:user_routes, {solar_system_id, %{routes: [], systems_static_data: []}}}
|
||||
end
|
||||
end)
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_ui_event(
|
||||
"set_autopilot_waypoint",
|
||||
%{
|
||||
|
||||
@@ -96,7 +96,11 @@ defmodule WandererAppWeb.MapSignaturesEventHandler do
|
||||
end)
|
||||
|
||||
{:noreply,
|
||||
socket |> MapEventHandler.push_map_event("init", %{system_signatures: system_signatures})}
|
||||
socket
|
||||
|> MapEventHandler.push_map_event(
|
||||
"map_updated",
|
||||
%{system_signatures: system_signatures}
|
||||
)}
|
||||
end
|
||||
|
||||
def handle_ui_event(
|
||||
|
||||
@@ -18,7 +18,7 @@ defmodule WandererAppWeb.MapStructuresEventHandler do
|
||||
def handle_ui_event(
|
||||
"get_structures",
|
||||
%{"system_id" => solar_system_id},
|
||||
%{assigns: %{map_id: map_id}} = socket
|
||||
%{assigns: %{map_id: map_id, map_loaded?: true}} = socket
|
||||
) do
|
||||
case MapSystem.read_by_map_and_solar_system(%{
|
||||
map_id: map_id,
|
||||
@@ -32,6 +32,14 @@ defmodule WandererAppWeb.MapStructuresEventHandler do
|
||||
end
|
||||
end
|
||||
|
||||
def handle_ui_event(
|
||||
"get_structures",
|
||||
_event,
|
||||
socket
|
||||
) do
|
||||
{:reply, %{structures: []}, socket}
|
||||
end
|
||||
|
||||
def handle_ui_event(
|
||||
"update_structures",
|
||||
%{
|
||||
|
||||
@@ -184,6 +184,97 @@ defmodule WandererAppWeb.MapSystemsEventHandler do
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_ui_event(
|
||||
"get_user_hubs",
|
||||
_event,
|
||||
%{
|
||||
assigns: %{
|
||||
map_id: map_id,
|
||||
current_user: current_user
|
||||
}
|
||||
} =
|
||||
socket
|
||||
) do
|
||||
{:ok, hubs} = WandererApp.MapUserSettingsRepo.get_hubs(map_id, current_user.id)
|
||||
|
||||
{:reply, %{hubs: hubs}, socket}
|
||||
end
|
||||
|
||||
def handle_ui_event(
|
||||
"add_user_hub",
|
||||
%{"system_id" => solar_system_id} = _event,
|
||||
%{
|
||||
assigns: %{
|
||||
map_id: map_id,
|
||||
current_user: current_user
|
||||
}
|
||||
} =
|
||||
socket
|
||||
) do
|
||||
{:ok, map} = map_id |> WandererApp.Map.get_map()
|
||||
hubs_limit = map |> Map.get(:hubs_limit, 20)
|
||||
|
||||
{:ok, hubs} = WandererApp.MapUserSettingsRepo.get_hubs(map_id, current_user.id)
|
||||
|
||||
if hubs |> Enum.count() < hubs_limit do
|
||||
hubs = hubs ++ ["#{solar_system_id}"]
|
||||
|
||||
{:ok, _} =
|
||||
WandererApp.MapUserSettingsRepo.update_hubs(
|
||||
map_id,
|
||||
current_user.id,
|
||||
hubs
|
||||
)
|
||||
|
||||
{:noreply, socket |> MapEventHandler.push_map_event(
|
||||
"map_updated",
|
||||
%{user_hubs: hubs}
|
||||
)}
|
||||
else
|
||||
|
||||
{:noreply, socket |> MapEventHandler.push_map_event(
|
||||
"map_updated",
|
||||
%{user_hubs: hubs}
|
||||
)}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_ui_event(
|
||||
"delete_user_hub",
|
||||
%{"system_id" => solar_system_id} = _event,
|
||||
%{
|
||||
assigns: %{
|
||||
map_id: map_id,
|
||||
current_user: current_user
|
||||
}
|
||||
} =
|
||||
socket
|
||||
) do
|
||||
{:ok, hubs} = WandererApp.MapUserSettingsRepo.get_hubs(map_id, current_user.id)
|
||||
|
||||
case hubs |> Enum.member?("#{solar_system_id}") do
|
||||
true ->
|
||||
hubs = hubs |> Enum.reject(fn hub -> hub == "#{solar_system_id}" end)
|
||||
|
||||
{:ok, _} =
|
||||
WandererApp.MapUserSettingsRepo.update_hubs(
|
||||
map_id,
|
||||
current_user.id,
|
||||
hubs
|
||||
)
|
||||
|
||||
{:noreply, socket |> MapEventHandler.push_map_event(
|
||||
"map_updated",
|
||||
%{user_hubs: hubs}
|
||||
)}
|
||||
_ ->
|
||||
{:noreply, socket |> MapEventHandler.push_map_event(
|
||||
"map_updated",
|
||||
%{user_hubs: hubs}
|
||||
)}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_ui_event(
|
||||
"update_system_position",
|
||||
position,
|
||||
|
||||
@@ -56,7 +56,10 @@ defmodule WandererAppWeb.MapEventHandler do
|
||||
"update_system_locked",
|
||||
"update_system_tag",
|
||||
"update_system_temporary_name",
|
||||
"update_system_status"
|
||||
"update_system_status",
|
||||
"get_user_hubs",
|
||||
"add_user_hub",
|
||||
"delete_user_hub"
|
||||
]
|
||||
|
||||
@map_system_comments_events [
|
||||
@@ -98,11 +101,13 @@ defmodule WandererAppWeb.MapEventHandler do
|
||||
]
|
||||
|
||||
@map_routes_events [
|
||||
:routes
|
||||
:routes,
|
||||
:user_routes
|
||||
]
|
||||
|
||||
@map_routes_ui_events [
|
||||
"get_routes",
|
||||
"get_user_routes",
|
||||
"set_autopilot_waypoint"
|
||||
]
|
||||
|
||||
@@ -286,6 +291,8 @@ defmodule WandererAppWeb.MapEventHandler do
|
||||
|
||||
def push_map_event(socket, _type, _body), do: socket
|
||||
|
||||
def map_ui_character_stat(nil), do: nil
|
||||
|
||||
def map_ui_character_stat(character),
|
||||
do:
|
||||
character
|
||||
|
||||
@@ -65,7 +65,7 @@ defmodule WandererAppWeb.MapLive do
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(
|
||||
:info,
|
||||
:warning,
|
||||
"You don't have main character set, please update it in tracking settings (top right icon)."
|
||||
)}
|
||||
|
||||
|
||||
@@ -11,11 +11,12 @@ defmodule WandererAppWeb.MapsLive do
|
||||
@impl true
|
||||
def mount(
|
||||
_params,
|
||||
%{"user_id" => user_id} = _session,
|
||||
_session,
|
||||
%{assigns: %{current_user: current_user}} = socket
|
||||
)
|
||||
when not is_nil(user_id) and is_connected?(socket) do
|
||||
{:ok, active_characters} = WandererApp.Api.Character.active_by_user(%{user_id: user_id})
|
||||
when not is_nil(current_user) and is_connected?(socket) do
|
||||
{:ok, active_characters} =
|
||||
WandererApp.Api.Character.active_by_user(%{user_id: current_user.id})
|
||||
|
||||
user_characters =
|
||||
active_characters
|
||||
@@ -33,7 +34,7 @@ defmodule WandererAppWeb.MapsLive do
|
||||
is_version_valid?: false
|
||||
)
|
||||
|> assign_async(:maps, fn ->
|
||||
_load_maps(current_user)
|
||||
load_maps(current_user)
|
||||
end)}
|
||||
end
|
||||
|
||||
@@ -42,10 +43,10 @@ defmodule WandererAppWeb.MapsLive do
|
||||
{:ok,
|
||||
socket
|
||||
|> assign(
|
||||
maps: [],
|
||||
characters: [],
|
||||
location: nil,
|
||||
is_version_valid?: false
|
||||
is_version_valid?: false,
|
||||
restrict_maps_creation?: WandererApp.Env.restrict_maps_creation?()
|
||||
)}
|
||||
end
|
||||
|
||||
@@ -102,7 +103,8 @@ defmodule WandererAppWeb.MapsLive do
|
||||
:edit,
|
||||
%{"slug" => map_slug} = _params,
|
||||
url
|
||||
) do
|
||||
)
|
||||
when not is_nil(current_user) do
|
||||
WandererApp.Maps.check_user_can_delete_map(map_slug, current_user)
|
||||
|> case do
|
||||
{:ok, map} ->
|
||||
@@ -136,7 +138,8 @@ defmodule WandererAppWeb.MapsLive do
|
||||
:settings,
|
||||
%{"slug" => map_slug} = _params,
|
||||
_url
|
||||
) do
|
||||
)
|
||||
when not is_nil(current_user) do
|
||||
WandererApp.Maps.check_user_can_delete_map(map_slug, current_user)
|
||||
|> case do
|
||||
{:ok, map} ->
|
||||
@@ -244,7 +247,12 @@ defmodule WandererAppWeb.MapsLive do
|
||||
{:noreply, socket |> assign(form: form)}
|
||||
end
|
||||
|
||||
def handle_event("create", %{"form" => form}, socket) do
|
||||
def handle_event(
|
||||
"create",
|
||||
%{"form" => form},
|
||||
%{assigns: %{current_user: current_user}} = socket
|
||||
)
|
||||
when not is_nil(current_user) do
|
||||
scope =
|
||||
form
|
||||
|> Map.get("scope")
|
||||
@@ -260,12 +268,10 @@ defmodule WandererAppWeb.MapsLive do
|
||||
:telemetry.execute([:wanderer_app, :map, :created], %{count: 1})
|
||||
maybe_create_default_acl(form, new_map)
|
||||
|
||||
current_user = socket.assigns.current_user
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign_async(:maps, fn ->
|
||||
_load_maps(current_user)
|
||||
load_maps(current_user)
|
||||
end)
|
||||
|> push_patch(to: ~p"/maps")}
|
||||
|
||||
@@ -419,7 +425,7 @@ defmodule WandererAppWeb.MapsLive do
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign_async(:maps, fn ->
|
||||
_load_maps(current_user)
|
||||
load_maps(current_user)
|
||||
end)
|
||||
|> push_patch(to: ~p"/maps")}
|
||||
end
|
||||
@@ -625,7 +631,7 @@ defmodule WandererAppWeb.MapsLive do
|
||||
target: "#{solar_system_target}"
|
||||
}
|
||||
|
||||
defp _load_maps(current_user) do
|
||||
defp load_maps(current_user) do
|
||||
{:ok, maps} = WandererApp.Maps.get_available_maps(current_user)
|
||||
|
||||
maps =
|
||||
|
||||
@@ -1,138 +1,136 @@
|
||||
<div class="grid grid-flow-row gap-2 p-3 h-full w-full pl-20">
|
||||
<main class="w-full rounded-lg shadow col-span-2 lg:col-span-1 overflow-auto p-3">
|
||||
<%= if @maps != [] do %>
|
||||
<div class="gap-4 grid grid-cols-2 lg:grid-cols-5 md:grid-cols-3 sm:grid-cols-3 ">
|
||||
<div class="gap-4 grid grid-cols-2 lg:grid-cols-5 md:grid-cols-3 sm:grid-cols-3 ">
|
||||
<.link
|
||||
:if={not @restrict_maps_creation?}
|
||||
class="card h-[250px] rounded-none bg-gradient-to-l from-stone-950 to-stone-900 hover:text-white transform transition duration-500"
|
||||
patch={~p"/maps/new"}
|
||||
>
|
||||
<div class="card-body justify-center items-center">
|
||||
<.icon name="hero-plus-solid" class="w-20 h-20" />
|
||||
<h3 class="card-title text-center text-md">Create Map</h3>
|
||||
</div>
|
||||
</.link>
|
||||
<.async_result :if={assigns[:maps]} :let={maps} assign={@maps}>
|
||||
<:loading>
|
||||
<div class="skeleton card rounded"></div>
|
||||
<div class="skeleton card rounded"></div>
|
||||
<div class="skeleton card rounded"></div>
|
||||
<div class="skeleton card rounded"></div>
|
||||
</:loading>
|
||||
<:failed :let={reason}><%= reason %></:failed>
|
||||
<.link
|
||||
:if={not @restrict_maps_creation?}
|
||||
class="card h-[250px] rounded-none bg-gradient-to-l from-stone-950 to-stone-900 hover:text-white transform transition duration-500"
|
||||
patch={~p"/maps/new"}
|
||||
:for={map <- maps}
|
||||
navigate={~p"/#{map.slug}"}
|
||||
class="card h-[250px] rounded-none bg-gradient-to-l from-stone-950 to-stone-900 hover:text-white"
|
||||
>
|
||||
<div class="card-body justify-center items-center">
|
||||
<.icon name="hero-plus-solid" class="w-20 h-20" />
|
||||
<h3 class="card-title text-center text-md">Create Map</h3>
|
||||
</div>
|
||||
</.link>
|
||||
<.async_result :let={maps} assign={@maps}>
|
||||
<:loading>
|
||||
<div class="skeleton card rounded"></div>
|
||||
<div class="skeleton card rounded"></div>
|
||||
<div class="skeleton card rounded"></div>
|
||||
<div class="skeleton card rounded"></div>
|
||||
</:loading>
|
||||
<:failed :let={reason}><%= reason %></:failed>
|
||||
<.link
|
||||
:for={map <- maps}
|
||||
navigate={~p"/#{map.slug}"}
|
||||
class="card h-[250px] rounded-none bg-gradient-to-l from-stone-950 to-stone-900 hover:text-white"
|
||||
>
|
||||
<figure class="absolute z-10 h-200 avatar w-full h-full">
|
||||
<img :if={map.scope === :all} class="absolute h-200" src="/images/all_back.webp" />
|
||||
<img
|
||||
:if={map.scope === :wormholes}
|
||||
class="absolute h-200"
|
||||
src="/images/wh_back.jpg"
|
||||
/>
|
||||
<img
|
||||
:if={map.scope === :stargates}
|
||||
class="absolute h-200"
|
||||
src="/images/stargates_back.webp"
|
||||
/>
|
||||
</figure>
|
||||
<figure class="absolute z-10 h-200 avatar w-full h-full">
|
||||
<img :if={map.scope === :all} class="absolute h-200" src="/images/all_back.webp" />
|
||||
<img
|
||||
:if={map.scope === :wormholes}
|
||||
class="absolute h-200"
|
||||
src="/images/wh_back.jpg"
|
||||
/>
|
||||
<img
|
||||
:if={map.scope === :stargates}
|
||||
class="absolute h-200"
|
||||
src="/images/stargates_back.webp"
|
||||
/>
|
||||
</figure>
|
||||
|
||||
<div class="absolute z-50 left-0 top-0 w-full h-full p-6 flex flex-col justify-between bg-opacity-70 bg-neutral-900 hover:bg-opacity-30 transform transition duration-500">
|
||||
<div>
|
||||
<h2 class="card-title text-sm">
|
||||
<%= map.name %>
|
||||
</h2>
|
||||
<p title={map.description} class="text-sm mt-4 line-clamp-2">
|
||||
<%= map.description %>
|
||||
</p>
|
||||
<div
|
||||
:if={WandererApp.Maps.can_view_acls?(map, @current_user)}
|
||||
class="w-full flex gap-2 mt-2 text-xs"
|
||||
<div class="absolute z-50 left-0 top-0 w-full h-full p-6 flex flex-col justify-between bg-opacity-70 bg-neutral-900 hover:bg-opacity-30 transform transition duration-500">
|
||||
<div>
|
||||
<h2 class="card-title text-sm">
|
||||
<%= map.name %>
|
||||
</h2>
|
||||
<p title={map.description} class="text-sm mt-4 line-clamp-2">
|
||||
<%= map.description %>
|
||||
</p>
|
||||
<div
|
||||
:if={WandererApp.Maps.can_view_acls?(map, @current_user)}
|
||||
class="w-full flex gap-2 mt-2 text-xs"
|
||||
>
|
||||
<button
|
||||
:for={acl <- map.acls}
|
||||
class="p-tag p-component rounded-none hover:text-white"
|
||||
id={"map-acl-#{acl.id}"}
|
||||
type="button"
|
||||
phx-hook="MapAction"
|
||||
data-event="open_acl"
|
||||
data-data={acl.id}
|
||||
>
|
||||
<button
|
||||
:for={acl <- map.acls}
|
||||
class="p-tag p-component rounded-none hover:text-white"
|
||||
id={"map-acl-#{acl.id}"}
|
||||
type="button"
|
||||
phx-hook="MapAction"
|
||||
data-event="open_acl"
|
||||
data-data={acl.id}
|
||||
>
|
||||
<div class="p-tag-value">
|
||||
<%= acl.name %>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="w-full flex justify-between mb-4 text-sm">
|
||||
Tracked Characters:
|
||||
<span class="font-bold">
|
||||
<%= map.characters_count %>
|
||||
</span>
|
||||
</h2>
|
||||
<div class="flex gap-2 justify-end">
|
||||
<button
|
||||
:if={WandererApp.Maps.can_edit?(map, @current_user)}
|
||||
id={"map-characters-#{map.slug}"}
|
||||
phx-hook="MapAction"
|
||||
data-event="open_characters"
|
||||
data-data={map.slug}
|
||||
class="h-8 w-8 hover:text-white"
|
||||
>
|
||||
<.icon name="hero-user-group-solid" class="w-6 h-6" />
|
||||
</button>
|
||||
<button
|
||||
:if={WandererApp.Maps.can_edit?(map, @current_user)}
|
||||
id={"map-audit-#{map.slug}"}
|
||||
phx-hook="MapAction"
|
||||
data-event="open_audit"
|
||||
data-data={map.slug}
|
||||
class="h-8 w-8 hover:text-white"
|
||||
>
|
||||
<.icon name="hero-key-solid" class="w-6 h-6" />
|
||||
</button>
|
||||
<button
|
||||
:if={WandererApp.Maps.can_edit?(map, @current_user)}
|
||||
id={"map-settings-#{map.slug}"}
|
||||
phx-hook="MapAction"
|
||||
data-event="open_settings"
|
||||
data-data={map.slug}
|
||||
class="h-8 w-8 hover:text-white"
|
||||
>
|
||||
<.icon name="hero-cog-6-tooth-solid" class="w-6 h-6" />
|
||||
</button>
|
||||
<button
|
||||
:if={WandererApp.Maps.can_edit?(map, @current_user)}
|
||||
id={"edit-map-#{map.slug}"}
|
||||
class="h-8 w-8 hover:text-white"
|
||||
type="button"
|
||||
phx-hook="MapAction"
|
||||
data-event="edit_map"
|
||||
data-data={map.slug}
|
||||
>
|
||||
<.icon name="hero-pencil-square-solid" class="w-6 h-6" />
|
||||
</button>
|
||||
<button
|
||||
:if={WandererApp.Maps.can_edit?(map, @current_user)}
|
||||
id={"delete-map-#{map.slug}"}
|
||||
class="h-8 w-8 hover:text-white"
|
||||
phx-hook="MapAction"
|
||||
data-event="delete"
|
||||
data-data={map.slug}
|
||||
data-confirm="Please confirm to delete map!"
|
||||
>
|
||||
<.icon name="hero-trash-solid" class="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="p-tag-value">
|
||||
<%= acl.name %>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</.link>
|
||||
</.async_result>
|
||||
</div>
|
||||
<% end %>
|
||||
<div>
|
||||
<h2 class="w-full flex justify-between mb-4 text-sm">
|
||||
Tracked Characters:
|
||||
<span class="font-bold">
|
||||
<%= map.characters_count %>
|
||||
</span>
|
||||
</h2>
|
||||
<div class="flex gap-2 justify-end">
|
||||
<button
|
||||
:if={WandererApp.Maps.can_edit?(map, @current_user)}
|
||||
id={"map-characters-#{map.slug}"}
|
||||
phx-hook="MapAction"
|
||||
data-event="open_characters"
|
||||
data-data={map.slug}
|
||||
class="h-8 w-8 hover:text-white"
|
||||
>
|
||||
<.icon name="hero-user-group-solid" class="w-6 h-6" />
|
||||
</button>
|
||||
<button
|
||||
:if={WandererApp.Maps.can_edit?(map, @current_user)}
|
||||
id={"map-audit-#{map.slug}"}
|
||||
phx-hook="MapAction"
|
||||
data-event="open_audit"
|
||||
data-data={map.slug}
|
||||
class="h-8 w-8 hover:text-white"
|
||||
>
|
||||
<.icon name="hero-key-solid" class="w-6 h-6" />
|
||||
</button>
|
||||
<button
|
||||
:if={WandererApp.Maps.can_edit?(map, @current_user)}
|
||||
id={"map-settings-#{map.slug}"}
|
||||
phx-hook="MapAction"
|
||||
data-event="open_settings"
|
||||
data-data={map.slug}
|
||||
class="h-8 w-8 hover:text-white"
|
||||
>
|
||||
<.icon name="hero-cog-6-tooth-solid" class="w-6 h-6" />
|
||||
</button>
|
||||
<button
|
||||
:if={WandererApp.Maps.can_edit?(map, @current_user)}
|
||||
id={"edit-map-#{map.slug}"}
|
||||
class="h-8 w-8 hover:text-white"
|
||||
type="button"
|
||||
phx-hook="MapAction"
|
||||
data-event="edit_map"
|
||||
data-data={map.slug}
|
||||
>
|
||||
<.icon name="hero-pencil-square-solid" class="w-6 h-6" />
|
||||
</button>
|
||||
<button
|
||||
:if={WandererApp.Maps.can_edit?(map, @current_user)}
|
||||
id={"delete-map-#{map.slug}"}
|
||||
class="h-8 w-8 hover:text-white"
|
||||
phx-hook="MapAction"
|
||||
data-event="delete"
|
||||
data-data={map.slug}
|
||||
data-confirm="Please confirm to delete map!"
|
||||
>
|
||||
<.icon name="hero-trash-solid" class="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</.link>
|
||||
</.async_result>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
|
||||
2
mix.exs
2
mix.exs
@@ -3,7 +3,7 @@ defmodule WandererApp.MixProject do
|
||||
|
||||
@source_url "https://github.com/wanderer-industries/wanderer"
|
||||
|
||||
@version "1.61.1"
|
||||
@version "1.62.2"
|
||||
|
||||
def project do
|
||||
[
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
defmodule WandererApp.Repo.Migrations.AddMapUserSettingsHubs do
|
||||
@moduledoc """
|
||||
Updates resources based on their most recent snapshots.
|
||||
|
||||
This file was autogenerated with `mix ash_postgres.generate_migrations`
|
||||
"""
|
||||
|
||||
use Ecto.Migration
|
||||
|
||||
def up do
|
||||
alter table(:map_user_settings_v1) do
|
||||
add :hubs, {:array, :text}, default: []
|
||||
end
|
||||
end
|
||||
|
||||
def down do
|
||||
alter table(:map_user_settings_v1) do
|
||||
remove :hubs
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,129 @@
|
||||
{
|
||||
"attributes": [
|
||||
{
|
||||
"allow_nil?": false,
|
||||
"default": "fragment(\"gen_random_uuid()\")",
|
||||
"generated?": false,
|
||||
"primary_key?": true,
|
||||
"references": null,
|
||||
"size": null,
|
||||
"source": "id",
|
||||
"type": "uuid"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"size": null,
|
||||
"source": "settings",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "[]",
|
||||
"generated?": false,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"size": null,
|
||||
"source": "hubs",
|
||||
"type": [
|
||||
"array",
|
||||
"text"
|
||||
]
|
||||
},
|
||||
{
|
||||
"allow_nil?": false,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"primary_key?": true,
|
||||
"references": {
|
||||
"deferrable": false,
|
||||
"destination_attribute": "id",
|
||||
"destination_attribute_default": null,
|
||||
"destination_attribute_generated": null,
|
||||
"index?": false,
|
||||
"match_type": null,
|
||||
"match_with": null,
|
||||
"multitenancy": {
|
||||
"attribute": null,
|
||||
"global": null,
|
||||
"strategy": null
|
||||
},
|
||||
"name": "map_user_settings_v1_map_id_fkey",
|
||||
"on_delete": null,
|
||||
"on_update": null,
|
||||
"primary_key?": true,
|
||||
"schema": "public",
|
||||
"table": "maps_v1"
|
||||
},
|
||||
"size": null,
|
||||
"source": "map_id",
|
||||
"type": "uuid"
|
||||
},
|
||||
{
|
||||
"allow_nil?": false,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"primary_key?": true,
|
||||
"references": {
|
||||
"deferrable": false,
|
||||
"destination_attribute": "id",
|
||||
"destination_attribute_default": null,
|
||||
"destination_attribute_generated": null,
|
||||
"index?": false,
|
||||
"match_type": null,
|
||||
"match_with": null,
|
||||
"multitenancy": {
|
||||
"attribute": null,
|
||||
"global": null,
|
||||
"strategy": null
|
||||
},
|
||||
"name": "map_user_settings_v1_user_id_fkey",
|
||||
"on_delete": null,
|
||||
"on_update": null,
|
||||
"primary_key?": true,
|
||||
"schema": "public",
|
||||
"table": "user_v1"
|
||||
},
|
||||
"size": null,
|
||||
"source": "user_id",
|
||||
"type": "uuid"
|
||||
}
|
||||
],
|
||||
"base_filter": null,
|
||||
"check_constraints": [],
|
||||
"custom_indexes": [],
|
||||
"custom_statements": [],
|
||||
"has_create_action": true,
|
||||
"hash": "4E38AE137F667F0707C49D15D5B79653DDF6F8A248D1845713200DE4E1C144F0",
|
||||
"identities": [
|
||||
{
|
||||
"all_tenants?": false,
|
||||
"base_filter": null,
|
||||
"index_name": "map_user_settings_v1_uniq_map_user_index",
|
||||
"keys": [
|
||||
{
|
||||
"type": "atom",
|
||||
"value": "map_id"
|
||||
},
|
||||
{
|
||||
"type": "atom",
|
||||
"value": "user_id"
|
||||
}
|
||||
],
|
||||
"name": "uniq_map_user",
|
||||
"nils_distinct?": true,
|
||||
"where": null
|
||||
}
|
||||
],
|
||||
"multitenancy": {
|
||||
"attribute": null,
|
||||
"global": null,
|
||||
"strategy": null
|
||||
},
|
||||
"repo": "Elixir.WandererApp.Repo",
|
||||
"schema": null,
|
||||
"table": "map_user_settings_v1"
|
||||
}
|
||||
Reference in New Issue
Block a user