mirror of
https://github.com/wanderer-industries/wanderer
synced 2025-12-04 23:05:35 +00:00
Compare commits
99 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ec92b19fb | ||
|
|
630caa8686 | ||
|
|
497c3b2165 | ||
|
|
8823d06893 | ||
|
|
72a2bf50df | ||
|
|
15c1ed2011 | ||
|
|
3fb19663cc | ||
|
|
00cdbbc89c | ||
|
|
07f3cec16d | ||
|
|
e893e25ff4 | ||
|
|
1ad9a1eb13 | ||
|
|
d9288596e8 | ||
|
|
a2a642d9ce | ||
|
|
4f00007108 | ||
|
|
816ee77b6f | ||
|
|
26d470ecda | ||
|
|
3babd9d95e | ||
|
|
802e81b1cd | ||
|
|
41f0834c51 | ||
|
|
880de0b047 | ||
|
|
bbe7fda4e0 | ||
|
|
4fd214e328 | ||
|
|
92a9274dce | ||
|
|
8765d83083 | ||
|
|
a298152bc8 | ||
|
|
2b7abe5774 | ||
|
|
3e9241892e | ||
|
|
a8dcdcf339 | ||
|
|
6ea79a7960 | ||
|
|
2af562e313 | ||
|
|
40672f6a47 | ||
|
|
6d66ae3f50 | ||
|
|
94c89e0325 | ||
|
|
3670ef40a3 | ||
|
|
16d464fba5 | ||
|
|
0b7e0b9cd0 | ||
|
|
dd5fd114d2 | ||
|
|
6e53879344 | ||
|
|
af2bfd4d59 | ||
|
|
a4a34c8ba7 | ||
|
|
8c609f4fdf | ||
|
|
197f5b583f | ||
|
|
4eb4a03e59 | ||
|
|
5719469452 | ||
|
|
6e9d525890 | ||
|
|
e82805dd48 | ||
|
|
3d4e66d438 | ||
|
|
ffbc9f169a | ||
|
|
99650187e9 | ||
|
|
92699317cd | ||
|
|
0e48315803 | ||
|
|
868ec246bd | ||
|
|
0030a688c6 | ||
|
|
3ba8f51a2f | ||
|
|
04576b335c | ||
|
|
ea29aa176f | ||
|
|
9a9b7289ba | ||
|
|
5edcd5572a | ||
|
|
d601790864 | ||
|
|
3cd9b819f5 | ||
|
|
bf58d3ae93 | ||
|
|
d6c32e2d39 | ||
|
|
6914f75bb7 | ||
|
|
3adf3946b5 | ||
|
|
bdc4948afb | ||
|
|
331db10029 | ||
|
|
c036a157c8 | ||
|
|
2daf9e34d2 | ||
|
|
acf35f8c51 | ||
|
|
9155515082 | ||
|
|
558cd9b8b3 | ||
|
|
a0f02d0d2f | ||
|
|
9feb8492aa | ||
|
|
e5aa726899 | ||
|
|
93d1c28ccd | ||
|
|
b5ba9200bc | ||
|
|
699d866670 | ||
|
|
c3071344cb | ||
|
|
9e998dd2b6 | ||
|
|
c9accf6079 | ||
|
|
1b41a51004 | ||
|
|
3338dce900 | ||
|
|
1364779f81 | ||
|
|
b49d3423fc | ||
|
|
cccab2a985 | ||
|
|
1abaa90a7d | ||
|
|
6e1993ca8a | ||
|
|
cb318aa6c6 | ||
|
|
61235828ce | ||
|
|
1a27b21efe | ||
|
|
e57f565812 | ||
|
|
da2605ee03 | ||
|
|
927c07bfd5 | ||
|
|
d00caf1f4c | ||
|
|
a5e9d72bc5 | ||
|
|
8ac9047831 | ||
|
|
65509ace59 | ||
|
|
ea173f971a | ||
|
|
c45c97c5d8 |
42
.github/workflows/build.yml
vendored
42
.github/workflows/build.yml
vendored
@@ -18,49 +18,8 @@ permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
deploy-test:
|
||||
name: 🚀 Deploy to test env (fly.io)
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.base_ref == 'main' || (github.ref == 'refs/heads/main' && github.event_name == 'push') }}
|
||||
steps:
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
- uses: superfly/flyctl-actions/setup-flyctl@master
|
||||
|
||||
- name: 👀 Read app name
|
||||
uses: SebRollen/toml-action@v1.0.0
|
||||
id: app_name
|
||||
with:
|
||||
file: "fly.toml"
|
||||
field: "app"
|
||||
|
||||
- name: 🚀 Deploy Test
|
||||
run: flyctl deploy --remote-only --wait-timeout=300 --ha=false
|
||||
env:
|
||||
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
|
||||
|
||||
manual-approval:
|
||||
name: Manual Approval
|
||||
runs-on: ubuntu-latest
|
||||
needs: deploy-test
|
||||
if: success()
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
steps:
|
||||
- name: Await Manual Approval
|
||||
uses: trstringer/manual-approval@v1
|
||||
with:
|
||||
secret: ${{ github.TOKEN }}
|
||||
approvers: DmitryPopov
|
||||
minimum-approvals: 1
|
||||
issue-title: "Manual Approval Required for Release"
|
||||
issue-body: "Please approve or deny the deployment."
|
||||
|
||||
build:
|
||||
name: 🛠 Build
|
||||
needs: manual-approval
|
||||
runs-on: ubuntu-22.04
|
||||
if: ${{ (github.ref == 'refs/heads/main') && github.event_name == 'push' }}
|
||||
permissions:
|
||||
@@ -157,6 +116,7 @@ jobs:
|
||||
matrix:
|
||||
platform:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
steps:
|
||||
- name: Prepare
|
||||
run: |
|
||||
|
||||
182
CHANGELOG.md
182
CHANGELOG.md
@@ -2,6 +2,188 @@
|
||||
|
||||
<!-- changelog -->
|
||||
|
||||
## [v1.65.11](https://github.com/wanderer-industries/wanderer/compare/v1.65.10...v1.65.11) (2025-05-27)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fixed showing character ship
|
||||
|
||||
## [v1.65.10](https://github.com/wanderer-industries/wanderer/compare/v1.65.9...v1.65.10) (2025-05-27)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fixed sorting for characters in Local
|
||||
|
||||
* Map: Rally: fixed conflict style of status and rally
|
||||
|
||||
* Core: Fixed character token refresh
|
||||
|
||||
* Map: Add Rally point. Change placement of settings in Map User Settings. Add ability to placement minimap.
|
||||
|
||||
* Map: Routes - hide user routes btn from context if subscriptions is not active or widget is closed. Also now hidden widget will show again in place where it was on moment of hide (except cases when screen size has changed.
|
||||
|
||||
* Map: PINGS - Rally point first prototype
|
||||
|
||||
## [v1.65.9](https://github.com/wanderer-industries/wanderer/compare/v1.65.8...v1.65.9) (2025-05-26)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.65.8](https://github.com/wanderer-industries/wanderer/compare/v1.65.7...v1.65.8) (2025-05-26)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.65.7](https://github.com/wanderer-industries/wanderer/compare/v1.65.6...v1.65.7) (2025-05-26)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed map character tracking issues
|
||||
|
||||
## [v1.65.6](https://github.com/wanderer-industries/wanderer/compare/v1.65.5...v1.65.6) (2025-05-26)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed map character tracking issues
|
||||
|
||||
## [v1.65.5](https://github.com/wanderer-industries/wanderer/compare/v1.65.4...v1.65.5) (2025-05-26)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed map character tracking issues
|
||||
|
||||
* Signature: Update restored signature character
|
||||
|
||||
## [v1.65.4](https://github.com/wanderer-industries/wanderer/compare/v1.65.3...v1.65.4) (2025-05-24)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Signature: Force signature update even if there are no any changes
|
||||
|
||||
## [v1.65.3](https://github.com/wanderer-industries/wanderer/compare/v1.65.2...v1.65.3) (2025-05-23)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Signature: Fixed signature clenup
|
||||
|
||||
## [v1.65.2](https://github.com/wanderer-industries/wanderer/compare/v1.65.1...v1.65.2) (2025-05-23)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Signature: Fixed signature updates
|
||||
|
||||
## [v1.65.1](https://github.com/wanderer-industries/wanderer/compare/v1.65.0...v1.65.1) (2025-05-22)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Added unsync map events timeout handling (force page refresh if outdated map events found)
|
||||
|
||||
## [v1.65.0](https://github.com/wanderer-industries/wanderer/compare/v1.64.8...v1.65.0) (2025-05-22)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* default connections from c1 holes to medium size
|
||||
|
||||
* support german and french signatures
|
||||
|
||||
* improve signature undo process
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* remove required id field from character schema
|
||||
|
||||
* update openapi spec response types
|
||||
|
||||
* fix issue with connection generation between k-space
|
||||
|
||||
* Signature: Fixed signatures updates
|
||||
|
||||
* update openapi spec for other apis
|
||||
|
||||
## [v1.64.8](https://github.com/wanderer-industries/wanderer/compare/v1.64.7...v1.64.8) (2025-05-20)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Added unsync map events timeout handling (force page refresh if outdated map events found)
|
||||
|
||||
## [v1.64.7](https://github.com/wanderer-industries/wanderer/compare/v1.64.6...v1.64.7) (2025-05-15)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed connection EOL time refreshed every 2 minutes
|
||||
|
||||
## [v1.64.6](https://github.com/wanderer-industries/wanderer/compare/v1.64.5...v1.64.6) (2025-05-15)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Added map hubs limits checking & a proper warning message shown
|
||||
|
||||
## [v1.64.5](https://github.com/wanderer-industries/wanderer/compare/v1.64.4...v1.64.5) (2025-05-14)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Added character name update on re-auth
|
||||
|
||||
## [v1.64.4](https://github.com/wanderer-industries/wanderer/compare/v1.64.3...v1.64.4) (2025-05-14)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Added 1 min timeout for ship and location updates on ESI API errors
|
||||
|
||||
## [v1.64.3](https://github.com/wanderer-industries/wanderer/compare/v1.64.2...v1.64.3) (2025-05-14)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed character tracking initialization logic & removed search caching
|
||||
|
||||
## [v1.64.2](https://github.com/wanderer-industries/wanderer/compare/v1.64.1...v1.64.2) (2025-05-13)
|
||||
|
||||
|
||||
|
||||
@@ -17,5 +17,6 @@ module.exports = {
|
||||
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
|
||||
'react/react-in-jsx-scope': 'off',
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
"linebreak-style": "off",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"semi": true,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"endOfLine": "lf"
|
||||
"endOfLine": "auto"
|
||||
}
|
||||
|
||||
@@ -198,3 +198,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.p-autocomplete .p-autocomplete-multiple-container:not(.p-disabled).p-focus {
|
||||
box-shadow: 0 0 0 1px #335c7e;
|
||||
border-color: #335c7e;
|
||||
}
|
||||
|
||||
.p-inputtext:enabled:focus {
|
||||
box-shadow: 0 0 0 1px #335c7e;
|
||||
border-color: #335c7e;
|
||||
}
|
||||
|
||||
.p-inputtext:enabled:hover {
|
||||
border-color: #335c7e;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { RefObject } from 'react';
|
||||
import { ContextMenu } from 'primereact/contextmenu';
|
||||
import { SolarSystemRawType } from '@/hooks/Mapper/types';
|
||||
import { PingType, SolarSystemRawType } from '@/hooks/Mapper/types';
|
||||
import { useContextMenuSystemItems } from '@/hooks/Mapper/components/contexts/ContextMenuSystem/useContextMenuSystemItems.tsx';
|
||||
import { WaypointSetContextHandler } from '@/hooks/Mapper/components/contexts/types.ts';
|
||||
|
||||
@@ -19,6 +19,7 @@ export interface ContextMenuSystemProps {
|
||||
onSystemStatus(val: number): void;
|
||||
onSystemLabels(val: string): void;
|
||||
onCustomLabelDialog(): void;
|
||||
onTogglePing(type: PingType, solar_system_id: string, hasPing: boolean): void;
|
||||
onWaypointSet: WaypointSetContextHandler;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './useTagMenu';
|
||||
export * from './useStatusMenu';
|
||||
export * from './useLabelsMenu';
|
||||
export * from './useUserRoute';
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { MapUserAddIcon, MapUserDeleteIcon } from '@/hooks/Mapper/icons';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { WidgetsIds } from '@/hooks/Mapper/components/mapInterface/constants.tsx';
|
||||
|
||||
interface UseUserRouteProps {
|
||||
systemId: string | undefined;
|
||||
userHubs: string[];
|
||||
onUserHubToggle(): void;
|
||||
}
|
||||
|
||||
export const useUserRoute = ({ userHubs, systemId, onUserHubToggle }: UseUserRouteProps) => {
|
||||
const {
|
||||
data: { isSubscriptionActive },
|
||||
windowsSettings,
|
||||
} = useMapRootState();
|
||||
|
||||
const ref = useRef({ userHubs, systemId, onUserHubToggle, isSubscriptionActive, windowsSettings });
|
||||
ref.current = { userHubs, systemId, onUserHubToggle, isSubscriptionActive, windowsSettings };
|
||||
|
||||
return useCallback(() => {
|
||||
const { userHubs, systemId, onUserHubToggle, isSubscriptionActive, windowsSettings } = ref.current;
|
||||
|
||||
const isVisibleUserRoutes = windowsSettings.visible.some(x => x === WidgetsIds.userRoutes);
|
||||
|
||||
if (!isSubscriptionActive || !isVisibleUserRoutes || !systemId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
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,
|
||||
},
|
||||
];
|
||||
}, [windowsSettings]);
|
||||
};
|
||||
@@ -5,6 +5,7 @@ import { SolarSystemRawType } from '@/hooks/Mapper/types';
|
||||
import { WaypointSetContextHandler } from '@/hooks/Mapper/components/contexts/types.ts';
|
||||
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
||||
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
|
||||
// import { PingType } from '@/hooks/Mapper/types/ping.ts';
|
||||
|
||||
interface UseContextMenuSystemHandlersProps {
|
||||
hubs: string[];
|
||||
@@ -93,6 +94,22 @@ export const useContextMenuSystemHandlers = ({
|
||||
setSystem(undefined);
|
||||
}, []);
|
||||
|
||||
// const onTogglePingRally = useCallback(() => {
|
||||
// const { userHubs, system, outCommand } = ref.current;
|
||||
// if (!system) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// outCommand({
|
||||
// type: OutCommand.openPing,
|
||||
// data: {
|
||||
// solar_system_id: system,
|
||||
// type: PingType.Rally,
|
||||
// },
|
||||
// });
|
||||
// setSystem(undefined);
|
||||
// }, []);
|
||||
|
||||
const onSystemTag = useCallback((tag?: string) => {
|
||||
const { system, outCommand } = ref.current;
|
||||
if (!system) {
|
||||
@@ -198,6 +215,7 @@ export const useContextMenuSystemHandlers = ({
|
||||
onLockToggle,
|
||||
onHubToggle,
|
||||
onUserHubToggle,
|
||||
// onTogglePingRally,
|
||||
onSystemTag,
|
||||
onSystemTemporaryName,
|
||||
onSystemStatus,
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { useLabelsMenu, useStatusMenu, useTagMenu } from '@/hooks/Mapper/components/contexts/ContextMenuSystem/hooks';
|
||||
import {
|
||||
useLabelsMenu,
|
||||
useStatusMenu,
|
||||
useTagMenu,
|
||||
useUserRoute,
|
||||
} from '@/hooks/Mapper/components/contexts/ContextMenuSystem/hooks';
|
||||
import { useMemo } from 'react';
|
||||
import { getSystemById } from '@/hooks/Mapper/helpers';
|
||||
import classes from './ContextMenuSystem.module.scss';
|
||||
@@ -10,13 +15,19 @@ 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';
|
||||
import { MapAddIcon, MapDeleteIcon } from '@/hooks/Mapper/icons';
|
||||
import { PingType } from '@/hooks/Mapper/types';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import clsx from 'clsx';
|
||||
import { MenuItem } from 'primereact/menuitem';
|
||||
import { MenuItemWithInfo, WdMenuItem } from '@/hooks/Mapper/components/ui-kit';
|
||||
|
||||
export const useContextMenuSystemItems = ({
|
||||
onDeleteSystem,
|
||||
onLockToggle,
|
||||
onHubToggle,
|
||||
onUserHubToggle,
|
||||
onTogglePing,
|
||||
onSystemTag,
|
||||
onSystemStatus,
|
||||
onSystemLabels,
|
||||
@@ -33,11 +44,32 @@ export const useContextMenuSystemItems = ({
|
||||
const getLabels = useLabelsMenu(systems, systemId, onSystemLabels, onCustomLabelDialog);
|
||||
const getWaypointMenu = useWaypointMenu(onWaypointSet);
|
||||
const canLockSystem = useMapCheckPermissions([UserPermission.LOCK_SYSTEM]);
|
||||
const canDeleteSystem = useMapCheckPermissions([UserPermission.DELETE_SYSTEM]);
|
||||
const getUserRoutes = useUserRoute({ userHubs, systemId, onUserHubToggle });
|
||||
|
||||
return useMemo(() => {
|
||||
const {
|
||||
data: { pings, isSubscriptionActive },
|
||||
} = useMapRootState();
|
||||
|
||||
const ping = useMemo(() => (pings.length === 1 ? pings[0] : undefined), [pings]);
|
||||
const isShowPingBtn = useMemo(() => {
|
||||
if (!isSubscriptionActive) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pings.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return pings[0].solar_system_id === systemId;
|
||||
}, [isSubscriptionActive, pings, systemId]);
|
||||
|
||||
return useMemo((): MenuItem[] => {
|
||||
const system = systemId ? getSystemById(systems, systemId) : undefined;
|
||||
const systemStaticInfo = getSystemStaticInfo(systemId)!;
|
||||
|
||||
const hasPing = ping?.solar_system_id === systemId;
|
||||
|
||||
if (!system || !systemId) {
|
||||
return [];
|
||||
}
|
||||
@@ -72,55 +104,87 @@ export const useContextMenuSystemItems = ({
|
||||
),
|
||||
command: onHubToggle,
|
||||
},
|
||||
...getUserRoutes(),
|
||||
|
||||
{ separator: true },
|
||||
{
|
||||
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,
|
||||
command: () => onTogglePing(PingType.Rally, systemId, hasPing),
|
||||
disabled: !isShowPingBtn,
|
||||
template: () => {
|
||||
const iconClasses = clsx({
|
||||
'pi text-cyan-400 hero-signal': !hasPing,
|
||||
'pi text-red-400 hero-signal-slash': hasPing,
|
||||
});
|
||||
|
||||
if (isShowPingBtn) {
|
||||
return <WdMenuItem icon={iconClasses}>{!hasPing ? 'Ping: RALLY' : 'Cancel: RALLY'}</WdMenuItem>;
|
||||
}
|
||||
|
||||
return (
|
||||
<MenuItemWithInfo
|
||||
infoTitle="Locked. Ping can be set only for one system."
|
||||
infoClass="pi-lock text-stone-500 mr-[12px]"
|
||||
>
|
||||
<WdMenuItem disabled icon={iconClasses}>
|
||||
{!hasPing ? 'Ping: RALLY' : 'Cancel: RALLY'}
|
||||
</WdMenuItem>
|
||||
</MenuItemWithInfo>
|
||||
);
|
||||
},
|
||||
},
|
||||
...(system.locked
|
||||
? canLockSystem
|
||||
? [
|
||||
{
|
||||
label: 'Unlock',
|
||||
icon: PrimeIcons.LOCK_OPEN,
|
||||
command: onLockToggle,
|
||||
},
|
||||
]
|
||||
: []
|
||||
: [
|
||||
...(canLockSystem
|
||||
? [
|
||||
{
|
||||
label: 'Lock',
|
||||
icon: PrimeIcons.LOCK,
|
||||
command: onLockToggle,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(canLockSystem
|
||||
? [
|
||||
{
|
||||
label: system.locked ? 'Unlock' : 'Lock',
|
||||
icon: system.locked ? PrimeIcons.LOCK_OPEN : PrimeIcons.LOCK,
|
||||
command: onLockToggle,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
||||
...(canDeleteSystem && !system.locked
|
||||
? [
|
||||
{ separator: true },
|
||||
{
|
||||
label: 'Delete',
|
||||
icon: PrimeIcons.TRASH,
|
||||
command: onDeleteSystem,
|
||||
disabled: hasPing,
|
||||
template: () => {
|
||||
if (!hasPing) {
|
||||
return <WdMenuItem icon="text-red-400 pi pi-trash">Delete</WdMenuItem>;
|
||||
}
|
||||
|
||||
return (
|
||||
<MenuItemWithInfo
|
||||
infoTitle="Locked. System can not be deleted until ping set."
|
||||
infoClass="pi-lock text-stone-500 mr-[12px]"
|
||||
>
|
||||
<WdMenuItem disabled icon="text-red-400 pi pi-trash">
|
||||
Delete
|
||||
</WdMenuItem>
|
||||
</MenuItemWithInfo>
|
||||
);
|
||||
},
|
||||
},
|
||||
]),
|
||||
]
|
||||
: []),
|
||||
];
|
||||
}, [
|
||||
canLockSystem,
|
||||
systems,
|
||||
systemId,
|
||||
systems,
|
||||
getTags,
|
||||
getStatus,
|
||||
getLabels,
|
||||
getWaypointMenu,
|
||||
getUserRoutes,
|
||||
hubs,
|
||||
onHubToggle,
|
||||
onOpenSettings,
|
||||
canLockSystem,
|
||||
onLockToggle,
|
||||
canDeleteSystem,
|
||||
onDeleteSystem,
|
||||
onOpenSettings,
|
||||
onTogglePing,
|
||||
ping,
|
||||
isShowPingBtn,
|
||||
]);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { LayoutEventBlocker, WdImageSize, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { LayoutEventBlocker, TooltipPosition, WdImageSize, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { ANOIK_ICON, DOTLAN_ICON, ZKB_ICON } from '@/hooks/Mapper/icons';
|
||||
|
||||
import classes from './FastSystemActions.module.scss';
|
||||
@@ -59,9 +59,21 @@ export const FastSystemActions = ({
|
||||
return (
|
||||
<LayoutEventBlocker className={clsx('flex px-2 gap-2 justify-between items-center h-full')}>
|
||||
<div className={clsx('flex gap-2 items-center h-full', classes.Links)}>
|
||||
<WdImgButton tooltip={{ content: 'Open zkillboard' }} source={ZKB_ICON} onClick={handleOpenZKB} />
|
||||
<WdImgButton tooltip={{ content: 'Open Anoikis' }} source={ANOIK_ICON} onClick={handleOpenAnoikis} />
|
||||
<WdImgButton tooltip={{ content: 'Open Dotlan' }} source={DOTLAN_ICON} onClick={handleOpenDotlan} />
|
||||
<WdImgButton
|
||||
tooltip={{ position: TooltipPosition.top, content: 'Open zkillboard' }}
|
||||
source={ZKB_ICON}
|
||||
onClick={handleOpenZKB}
|
||||
/>
|
||||
<WdImgButton
|
||||
tooltip={{ position: TooltipPosition.top, content: 'Open Anoikis' }}
|
||||
source={ANOIK_ICON}
|
||||
onClick={handleOpenAnoikis}
|
||||
/>
|
||||
<WdImgButton
|
||||
tooltip={{ position: TooltipPosition.top, content: 'Open Dotlan' }}
|
||||
source={DOTLAN_ICON}
|
||||
onClick={handleOpenDotlan}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 items-center pl-1">
|
||||
@@ -69,14 +81,14 @@ export const FastSystemActions = ({
|
||||
textSize={WdImageSize.off}
|
||||
className={PrimeIcons.COPY}
|
||||
onClick={copySystemNameToClipboard}
|
||||
tooltip={{ content: 'Copy system name' }}
|
||||
tooltip={{ position: TooltipPosition.top, content: 'Copy system name' }}
|
||||
/>
|
||||
{showEdit && (
|
||||
<WdImgButton
|
||||
textSize={WdImageSize.off}
|
||||
className="pi pi-pen-to-square text-base"
|
||||
onClick={onOpenSettings}
|
||||
tooltip={{ content: 'Edit system name and description' }}
|
||||
tooltip={{ position: TooltipPosition.top, content: 'Edit system name and description' }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -28,11 +28,12 @@ import {
|
||||
import { getBehaviorForTheme } from './helpers/getThemeBehavior';
|
||||
import { OnMapAddSystemCallback, OnMapSelectionChange } from './map.types';
|
||||
import { SESSION_KEY } from '@/hooks/Mapper/constants.ts';
|
||||
import { SolarSystemConnection, SolarSystemRawType } from '@/hooks/Mapper/types';
|
||||
import { PingData, SolarSystemConnection, SolarSystemRawType } from '@/hooks/Mapper/types';
|
||||
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
||||
import { NodeSelectionMouseHandler } from '@/hooks/Mapper/components/contexts/types.ts';
|
||||
import clsx from 'clsx';
|
||||
import { useBackgroundVars } from './hooks/useBackgroundVars';
|
||||
import type { PanelPosition } from '@reactflow/core';
|
||||
|
||||
const DEFAULT_VIEW_PORT = { zoom: 1, x: 0, y: 0 };
|
||||
|
||||
@@ -95,6 +96,8 @@ interface MapCompProps {
|
||||
isShowBackgroundPattern?: boolean;
|
||||
isSoftBackground?: boolean;
|
||||
theme?: string;
|
||||
pings: PingData[];
|
||||
minimapPlacement?: PanelPosition;
|
||||
}
|
||||
|
||||
const MapComp = ({
|
||||
@@ -112,6 +115,8 @@ const MapComp = ({
|
||||
isSoftBackground,
|
||||
theme,
|
||||
onAddSystem,
|
||||
pings,
|
||||
minimapPlacement = 'bottom-right',
|
||||
}: MapCompProps) => {
|
||||
const { getNodes } = useReactFlow();
|
||||
const [nodes, , onNodesChange] = useNodesState<Node<SolarSystemRawType>>(initialNodes);
|
||||
@@ -206,8 +211,9 @@ const MapComp = ({
|
||||
...x,
|
||||
showKSpaceBG: showKSpaceBG,
|
||||
isThickConnections: isThickConnections,
|
||||
pings,
|
||||
}));
|
||||
}, [showKSpaceBG, isThickConnections, update]);
|
||||
}, [showKSpaceBG, isThickConnections, pings, update]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -270,7 +276,9 @@ const MapComp = ({
|
||||
// onlyRenderVisibleElements
|
||||
selectionMode={SelectionMode.Partial}
|
||||
>
|
||||
{isShowMinimap && <MiniMap pannable zoomable ariaLabel="Mini map" className={minimapClasses} />}
|
||||
{isShowMinimap && (
|
||||
<MiniMap pannable zoomable ariaLabel="Mini map" className={minimapClasses} position={minimapPlacement} />
|
||||
)}
|
||||
{isShowBackgroundPattern && <Background variant={variant} gap={gap} size={size} color={color} />}
|
||||
</ReactFlow>
|
||||
{/* <button className="z-auto btn btn-primary absolute top-20 right-20" onClick={handleGetPassages}>
|
||||
|
||||
@@ -40,6 +40,8 @@ const INITIAL_DATA: MapData = {
|
||||
isSubscriptionActive: false,
|
||||
mainCharacterEveId: null,
|
||||
followingCharacterEveId: null,
|
||||
userHubs: [],
|
||||
pings: [],
|
||||
};
|
||||
|
||||
export interface MapContextProps {
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
@import '@/hooks/Mapper/components/map/styles/eve-common-variables';
|
||||
|
||||
$pastel-blue: #5a7d9a;
|
||||
$pastel-pink: #d291bc;
|
||||
$pastel-pink: rgb(30, 161, 255);
|
||||
$dark-bg: #2d2d2d;
|
||||
$text-color: #ffffff;
|
||||
$tooltip-bg: #202020;
|
||||
|
||||
$neon-color-1: rgb(27, 132, 236);
|
||||
$neon-color-3: rgba(27, 132, 236, 0.40);
|
||||
|
||||
@keyframes move-stripes {
|
||||
from {
|
||||
background-position: 0 0;
|
||||
}
|
||||
to {
|
||||
background-position: 30px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.RootCustomNode {
|
||||
display: flex;
|
||||
width: 130px;
|
||||
@@ -32,7 +44,7 @@ $tooltip-bg: #202020;
|
||||
&.Amarria,
|
||||
&.Gallente,
|
||||
&.Caldaria {
|
||||
&::before {
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -48,7 +60,7 @@ $tooltip-bg: #202020;
|
||||
}
|
||||
|
||||
&.Mataria {
|
||||
&::before {
|
||||
&::after {
|
||||
background-image: url('/images/mataria-180.png');
|
||||
opacity: 0.6;
|
||||
background-position-x: 1px;
|
||||
@@ -57,7 +69,7 @@ $tooltip-bg: #202020;
|
||||
}
|
||||
|
||||
&.Caldaria {
|
||||
&::before {
|
||||
&::after {
|
||||
background-image: url('/images/caldaria-180.png');
|
||||
opacity: 0.6;
|
||||
background-position-x: 1px;
|
||||
@@ -66,7 +78,7 @@ $tooltip-bg: #202020;
|
||||
}
|
||||
|
||||
&.Amarria {
|
||||
&::before {
|
||||
&::after {
|
||||
opacity: 0.45;
|
||||
background-image: url('/images/amarr-180.png');
|
||||
background-position-x: 0;
|
||||
@@ -75,7 +87,7 @@ $tooltip-bg: #202020;
|
||||
}
|
||||
|
||||
&.Gallente {
|
||||
&::before {
|
||||
&::after {
|
||||
opacity: 0.5;
|
||||
background-image: url('/images/gallente-180.png');
|
||||
background-position-x: 1px;
|
||||
@@ -88,6 +100,29 @@ $tooltip-bg: #202020;
|
||||
box-shadow: 0 0 10px #9a1af1c2;
|
||||
}
|
||||
|
||||
&.rally {
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
|
||||
border-color: $neon-color-1;
|
||||
background: repeating-linear-gradient(
|
||||
45deg,
|
||||
$neon-color-3 0px,
|
||||
$neon-color-3 8px,
|
||||
transparent 8px,
|
||||
transparent 21px
|
||||
);
|
||||
background-size: 30px 30px;
|
||||
animation: move-stripes 3s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
&.eve-system-status-home {
|
||||
border: 1px solid var(--eve-solar-system-status-color-home-dark30);
|
||||
background-image: linear-gradient(45deg, var(--eve-solar-system-status-color-background), transparent);
|
||||
|
||||
@@ -71,8 +71,11 @@ export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>
|
||||
className={clsx(
|
||||
classes.RootCustomNode,
|
||||
nodeVars.regionClass && classes[nodeVars.regionClass],
|
||||
nodeVars.status !== undefined ? classes[STATUS_CLASSES[nodeVars.status]] : '',
|
||||
{ [classes.selected]: nodeVars.selected },
|
||||
nodeVars.status !== undefined && classes[STATUS_CLASSES[nodeVars.status]],
|
||||
{
|
||||
[classes.selected]: nodeVars.selected,
|
||||
[classes.rally]: nodeVars.isRally,
|
||||
},
|
||||
)}
|
||||
onMouseDownCapture={e => nodeVars.dbClick(e)}
|
||||
>
|
||||
|
||||
@@ -71,7 +71,10 @@ export const SolarSystemNodeTheme = memo((props: NodeProps<MapSolarSystemType>)
|
||||
classes.RootCustomNode,
|
||||
nodeVars.regionClass && classes[nodeVars.regionClass],
|
||||
nodeVars.status !== undefined ? classes[STATUS_CLASSES[nodeVars.status]] : '',
|
||||
{ [classes.selected]: nodeVars.selected },
|
||||
{
|
||||
[classes.selected]: nodeVars.selected,
|
||||
[classes.rally]: nodeVars.isRally,
|
||||
},
|
||||
)}
|
||||
onMouseDownCapture={e => nodeVars.dbClick(e)}
|
||||
>
|
||||
@@ -116,23 +119,13 @@ export const SolarSystemNodeTheme = memo((props: NodeProps<MapSolarSystemType>)
|
||||
|
||||
<div className={clsx(classes.BottomRow, 'flex items-center justify-between')}>
|
||||
{nodeVars.customName && (
|
||||
<div
|
||||
className={clsx(
|
||||
classes.CustomName,
|
||||
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] whitespace-nowrap overflow-hidden text-ellipsis mr-0.5',
|
||||
)}
|
||||
>
|
||||
<div className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] whitespace-nowrap overflow-hidden text-ellipsis mr-0.5">
|
||||
{nodeVars.customName}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!nodeVars.isWormhole && !nodeVars.customName && (
|
||||
<div
|
||||
className={clsx(
|
||||
classes.RegionName,
|
||||
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] whitespace-nowrap overflow-hidden text-ellipsis mr-0.5',
|
||||
)}
|
||||
>
|
||||
<div className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] whitespace-nowrap overflow-hidden text-ellipsis mr-0.5">
|
||||
{nodeVars.regionName}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -115,35 +115,18 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
|
||||
}, 500);
|
||||
break;
|
||||
|
||||
case Commands.pingAdded:
|
||||
case Commands.pingCancelled:
|
||||
case Commands.routes:
|
||||
// do nothing here
|
||||
break;
|
||||
|
||||
case Commands.signaturesUpdated:
|
||||
// do nothing here
|
||||
break;
|
||||
|
||||
case Commands.linkSignatureToSystem:
|
||||
// do nothing here
|
||||
break;
|
||||
|
||||
case Commands.detailedKillsUpdated:
|
||||
// do nothing here
|
||||
break;
|
||||
|
||||
case Commands.characterActivityData:
|
||||
break;
|
||||
|
||||
case Commands.trackingCharactersData:
|
||||
break;
|
||||
|
||||
case Commands.updateActivity:
|
||||
break;
|
||||
|
||||
case Commands.updateTracking:
|
||||
break;
|
||||
|
||||
case Commands.userSettingsUpdated:
|
||||
// do nothing
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
@@ -9,7 +9,7 @@ import { REGIONS_MAP, Spaces } from '@/hooks/Mapper/constants';
|
||||
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace';
|
||||
import { getSystemClassStyles } from '@/hooks/Mapper/components/map/helpers';
|
||||
import { sortWHClasses } from '@/hooks/Mapper/helpers';
|
||||
import { CharacterTypeRaw, OutCommand, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { CharacterTypeRaw, OutCommand, PingType, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { useUnsplashedSignatures } from './useUnsplashedSignatures';
|
||||
import { useSystemName } from './useSystemName';
|
||||
import { LabelInfo, useLabelsInfo } from './useLabelsInfo';
|
||||
@@ -21,6 +21,45 @@ function getActivityType(count: number): string {
|
||||
return 'activityDanger';
|
||||
}
|
||||
|
||||
export interface SolarSystemNodeVars {
|
||||
id: string;
|
||||
selected: boolean;
|
||||
visible: boolean;
|
||||
isWormhole: boolean;
|
||||
classTitleColor: string | null;
|
||||
killsCount: number | null;
|
||||
killsActivityType: string | null;
|
||||
hasUserCharacters: boolean;
|
||||
showHandlers: boolean;
|
||||
regionClass: string | null;
|
||||
systemName: string;
|
||||
customName?: string | null;
|
||||
labelCustom: string | null;
|
||||
isShattered: boolean;
|
||||
tag?: string | null;
|
||||
status?: number;
|
||||
labelsInfo: LabelInfo[];
|
||||
dbClick: (event: React.MouseEvent<HTMLDivElement>) => void;
|
||||
sortedStatics: Array<string | number>;
|
||||
effectName: string | null;
|
||||
regionName: string | null;
|
||||
solarSystemId: string;
|
||||
solarSystemName: string | null;
|
||||
locked: boolean;
|
||||
hubs: string[];
|
||||
name: string | null;
|
||||
isConnecting: boolean;
|
||||
hoverNodeId: string | null;
|
||||
charactersInSystem: Array<CharacterTypeRaw>;
|
||||
userCharacters: string[];
|
||||
unsplashedLeft: Array<SystemSignature>;
|
||||
unsplashedRight: Array<SystemSignature>;
|
||||
isThickConnections: boolean;
|
||||
isRally: boolean;
|
||||
classTitle: string | null;
|
||||
temporaryName?: string | null;
|
||||
}
|
||||
|
||||
const SpaceToClass: Record<string, string> = {
|
||||
[Spaces.Caldari]: 'Caldaria',
|
||||
[Spaces.Matar]: 'Mataria',
|
||||
@@ -41,7 +80,7 @@ export function useLocalCounter(nodeVars: SolarSystemNodeVars) {
|
||||
return { localCounterCharacters };
|
||||
}
|
||||
|
||||
export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarSystemNodeVars {
|
||||
export const useSolarSystemNode = (props: NodeProps<MapSolarSystemType>): SolarSystemNodeVars => {
|
||||
const { id, data, selected } = props;
|
||||
const {
|
||||
id: solar_system_id,
|
||||
@@ -92,6 +131,7 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarS
|
||||
visibleNodes,
|
||||
showKSpaceBG,
|
||||
isThickConnections,
|
||||
pings,
|
||||
},
|
||||
outCommand,
|
||||
} = useMapState();
|
||||
@@ -154,6 +194,11 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarS
|
||||
|
||||
const hubsAsStrings = useMemo(() => hubs.map(item => item.toString()), [hubs]);
|
||||
|
||||
const isRally = useMemo(
|
||||
() => pings.find(x => x.solar_system_id === solar_system_id && x.type === PingType.Rally),
|
||||
[pings, solar_system_id],
|
||||
);
|
||||
|
||||
const nodeVars: SolarSystemNodeVars = {
|
||||
id,
|
||||
selected,
|
||||
@@ -190,45 +235,8 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarS
|
||||
temporaryName: computedTemporaryName,
|
||||
regionName: region_name,
|
||||
solarSystemName: solar_system_name,
|
||||
isRally,
|
||||
};
|
||||
|
||||
return nodeVars;
|
||||
}
|
||||
|
||||
export interface SolarSystemNodeVars {
|
||||
id: string;
|
||||
selected: boolean;
|
||||
visible: boolean;
|
||||
isWormhole: boolean;
|
||||
classTitleColor: string | null;
|
||||
killsCount: number | null;
|
||||
killsActivityType: string | null;
|
||||
hasUserCharacters: boolean;
|
||||
showHandlers: boolean;
|
||||
regionClass: string | null;
|
||||
systemName: string;
|
||||
customName?: string | null;
|
||||
labelCustom: string | null;
|
||||
isShattered: boolean;
|
||||
tag?: string | null;
|
||||
status?: number;
|
||||
labelsInfo: LabelInfo[];
|
||||
dbClick: (event: React.MouseEvent<HTMLDivElement>) => void;
|
||||
sortedStatics: Array<string | number>;
|
||||
effectName: string | null;
|
||||
regionName: string | null;
|
||||
solarSystemId: string;
|
||||
solarSystemName: string | null;
|
||||
locked: boolean;
|
||||
hubs: string[];
|
||||
name: string | null;
|
||||
isConnecting: boolean;
|
||||
hoverNodeId: string | null;
|
||||
charactersInSystem: Array<CharacterTypeRaw>;
|
||||
userCharacters: string[];
|
||||
unsplashedLeft: Array<SystemSignature>;
|
||||
unsplashedRight: Array<SystemSignature>;
|
||||
isThickConnections: boolean;
|
||||
classTitle: string | null;
|
||||
temporaryName?: string | null;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import classes from './MarkdownComment.module.scss';
|
||||
import clsx from 'clsx';
|
||||
import Markdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import { InfoDrawer, TimeAgo, TooltipPosition, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
|
||||
import remarkBreaks from 'remark-breaks';
|
||||
import {
|
||||
InfoDrawer,
|
||||
MarkdownTextViewer,
|
||||
TimeAgo,
|
||||
TooltipPosition,
|
||||
WdImgButton,
|
||||
} from '@/hooks/Mapper/components/ui-kit';
|
||||
import { useGetCacheCharacter } from '@/hooks/Mapper/mapRootProvider/hooks/api';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { WdTransition } from '@/hooks/Mapper/components/ui-kit/WdTransition/WdTransition.tsx';
|
||||
@@ -13,7 +16,6 @@ import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { OutCommand } from '@/hooks/Mapper/types';
|
||||
|
||||
const TOOLTIP_PROPS = { content: 'Remove comment', position: TooltipPosition.top };
|
||||
const REMARK_PLUGINS = [remarkGfm, remarkBreaks];
|
||||
|
||||
export interface MarkdownCommentProps {
|
||||
text: string;
|
||||
@@ -79,7 +81,7 @@ export const MarkdownComment = ({ text, time, characterEveId, id }: MarkdownComm
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Markdown remarkPlugins={REMARK_PLUGINS}>{text}</Markdown>
|
||||
<MarkdownTextViewer>{text}</MarkdownTextViewer>
|
||||
</InfoDrawer>
|
||||
|
||||
<ConfirmPopup
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
|
||||
export interface CommentsEditorProps {}
|
||||
|
||||
// eslint-disable-next-line no-empty-pattern
|
||||
export const CommentsEditor = ({}: CommentsEditorProps) => {
|
||||
const [textVal, setTextVal] = useState('');
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useMemo } from 'react';
|
||||
import { RoutesList } from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/RoutesList';
|
||||
|
||||
export const PingRoute = () => {
|
||||
const {
|
||||
data: { routes, pings, loadingPublicRoutes },
|
||||
} = useMapRootState();
|
||||
|
||||
const route = useMemo(() => {
|
||||
const [ping] = pings;
|
||||
if (!ping) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return routes?.routes.find(x => ping.solar_system_id === x.destination.toString()) ?? null;
|
||||
}, [routes, pings]);
|
||||
|
||||
const preparedRoute = useMemo(() => {
|
||||
if (!route) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...route,
|
||||
mapped_systems:
|
||||
route.systems?.map(solar_system_id =>
|
||||
routes?.systems_static_data.find(
|
||||
system_static_data => system_static_data.solar_system_id === solar_system_id,
|
||||
),
|
||||
) ?? [],
|
||||
};
|
||||
}, [route, routes?.systems_static_data]);
|
||||
|
||||
if (loadingPublicRoutes) {
|
||||
return <span className="m-0 text-[12px]">Loading...</span>;
|
||||
}
|
||||
|
||||
if (!preparedRoute || preparedRoute.origin === preparedRoute.destination) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="m-0 flex gap-2 items-center text-[12px]">
|
||||
{preparedRoute.has_connection && <div className="text-[12px]">{preparedRoute.systems?.length ?? 2}</div>}
|
||||
<RoutesList data={preparedRoute} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,284 @@
|
||||
import { Button } from 'primereact/button';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Toast } from 'primereact/toast';
|
||||
import clsx from 'clsx';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { Commands, OutCommand, PingType } from '@/hooks/Mapper/types';
|
||||
import {
|
||||
CharacterCardById,
|
||||
SystemView,
|
||||
TimeAgo,
|
||||
TooltipPosition,
|
||||
WdImgButton,
|
||||
WdImgButtonTooltip,
|
||||
} from '@/hooks/Mapper/components/ui-kit';
|
||||
import useRefState from 'react-usestateref';
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { emitMapEvent } from '@/hooks/Mapper/events';
|
||||
import { ConfirmPopup } from 'primereact/confirmpopup';
|
||||
import { PingRoute } from '@/hooks/Mapper/components/mapInterface/components/PingsInterface/PingRoute.tsx';
|
||||
import { PingsPlacement } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
|
||||
const PING_PLACEMENT_MAP = {
|
||||
[PingsPlacement.rightTop]: 'top-right',
|
||||
[PingsPlacement.leftTop]: 'top-left',
|
||||
[PingsPlacement.rightBottom]: 'bottom-right',
|
||||
[PingsPlacement.leftBottom]: 'bottom-left',
|
||||
};
|
||||
|
||||
const PING_PLACEMENT_MAP_OFFSETS = {
|
||||
[PingsPlacement.rightTop]: { default: '!top-[56px]', withLeftMenu: '!top-[56px] !right-[64px]' },
|
||||
[PingsPlacement.rightBottom]: { default: '!bottom-[15px]', withLeftMenu: '!bottom-[15px] !right-[64px]' },
|
||||
[PingsPlacement.leftTop]: { default: '!top-[56px] !left-[64px]', withLeftMenu: '!top-[56px] !left-[64px]' },
|
||||
[PingsPlacement.leftBottom]: { default: '!left-[64px] !bottom-[15px]', withLeftMenu: '!bottom-[15px]' },
|
||||
};
|
||||
|
||||
const CLOSE_TOOLTIP_PROPS: WdImgButtonTooltip = {
|
||||
content: 'Hide',
|
||||
position: TooltipPosition.top,
|
||||
className: '!leading-[0]',
|
||||
};
|
||||
|
||||
const NAVIGATE_TOOLTIP_PROPS: WdImgButtonTooltip = {
|
||||
content: 'Navigate To',
|
||||
position: TooltipPosition.top,
|
||||
className: '!leading-[0]',
|
||||
};
|
||||
|
||||
const DELETE_TOOLTIP_PROPS: WdImgButtonTooltip = {
|
||||
content: 'Remove',
|
||||
position: TooltipPosition.top,
|
||||
className: '!leading-[0]',
|
||||
};
|
||||
|
||||
// const TOOLTIP_WAYPOINT_PROPS: WdImgButtonTooltip = {
|
||||
// content: 'Waypoint',
|
||||
// position: TooltipPosition.bottom,
|
||||
// className: '!leading-[0]',
|
||||
// };
|
||||
|
||||
const TITLES = {
|
||||
[PingType.Alert]: 'Alert',
|
||||
[PingType.Rally]: 'Rally Point',
|
||||
};
|
||||
|
||||
const ICONS = {
|
||||
[PingType.Alert]: 'pi-bell',
|
||||
[PingType.Rally]: 'pi-bell',
|
||||
};
|
||||
|
||||
export interface PingsInterfaceProps {
|
||||
hasLeftOffset?: boolean;
|
||||
}
|
||||
|
||||
// TODO: right now can be one ping. But in future will be multiple pings then:
|
||||
// 1. we will use this as container
|
||||
// 2. we will create PingInstance (which will contains ping Button and Toast
|
||||
// 3. ADD Context menu
|
||||
export const PingsInterface = ({ hasLeftOffset }: PingsInterfaceProps) => {
|
||||
const toast = useRef<Toast>(null);
|
||||
const [isShow, setIsShow, isShowRef] = useRefState(false);
|
||||
|
||||
const cpRemoveBtnRef = useRef<HTMLElement>();
|
||||
const [cpRemoveVisible, setCpRemoveVisible] = useState(false);
|
||||
|
||||
const {
|
||||
storedSettings: { interfaceSettings },
|
||||
data: { pings, selectedSystems },
|
||||
outCommand,
|
||||
} = useMapRootState();
|
||||
|
||||
const selectedSystem = useMemo(() => {
|
||||
if (selectedSystems.length !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return selectedSystems[0];
|
||||
}, [selectedSystems]);
|
||||
|
||||
const ping = useMemo(() => (pings.length === 1 ? pings[0] : null), [pings]);
|
||||
|
||||
const handleShowCP = useCallback(() => setCpRemoveVisible(true), []);
|
||||
const handleHideCP = useCallback(() => setCpRemoveVisible(false), []);
|
||||
|
||||
const navigateTo = useCallback(() => {
|
||||
if (!ping) {
|
||||
return;
|
||||
}
|
||||
|
||||
emitMapEvent({
|
||||
name: Commands.centerSystem,
|
||||
data: ping.solar_system_id?.toString(),
|
||||
});
|
||||
}, [ping]);
|
||||
|
||||
const removePing = useCallback(async () => {
|
||||
if (!ping) {
|
||||
return;
|
||||
}
|
||||
|
||||
await outCommand({
|
||||
type: OutCommand.cancelPing,
|
||||
data: { type: ping.type, solar_system_id: ping.solar_system_id },
|
||||
});
|
||||
}, [outCommand, ping]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ping) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tid = setTimeout(() => {
|
||||
toast.current?.replace({ severity: 'warn', detail: ping.message });
|
||||
setIsShow(true);
|
||||
}, 200);
|
||||
|
||||
return () => clearTimeout(tid);
|
||||
}, [ping]);
|
||||
|
||||
const handleClickShow = useCallback(() => {
|
||||
if (!ping) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isShowRef.current) {
|
||||
toast.current?.show({ severity: 'warn', detail: ping.message });
|
||||
setIsShow(true);
|
||||
return;
|
||||
}
|
||||
toast.current?.clear();
|
||||
setIsShow(false);
|
||||
}, [ping]);
|
||||
|
||||
const handleClickHide = useCallback(() => {
|
||||
toast.current?.clear();
|
||||
setIsShow(false);
|
||||
}, []);
|
||||
|
||||
const { placement, offsets } = useMemo(() => {
|
||||
const rawPlacement =
|
||||
interfaceSettings.pingsPlacement == null ? PingsPlacement.rightTop : interfaceSettings.pingsPlacement;
|
||||
|
||||
return {
|
||||
placement: PING_PLACEMENT_MAP[rawPlacement],
|
||||
offsets: PING_PLACEMENT_MAP_OFFSETS[rawPlacement],
|
||||
};
|
||||
}, [interfaceSettings]);
|
||||
|
||||
if (!ping) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isShowSelectedSystem = selectedSystem != null && selectedSystem !== ping.solar_system_id;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Toast
|
||||
position={placement as never}
|
||||
className={clsx('!max-w-[initial] w-[500px]', hasLeftOffset ? offsets.withLeftMenu : offsets.default)}
|
||||
ref={toast}
|
||||
content={({ message }) => (
|
||||
<section
|
||||
className={clsx(
|
||||
'flex flex-col p-3 w-full border border-stone-800 shadow-md animate-fadeInDown rounded-[5px]',
|
||||
'bg-gradient-to-tr from-transparent to-sky-700/60 bg-stone-900/70',
|
||||
)}
|
||||
>
|
||||
<div className="flex gap-3">
|
||||
<i className={clsx('pi text-yellow-500 text-2xl', 'relative top-[2px]', ICONS[ping.type])}></i>
|
||||
<div className="flex flex-col gap-1 w-full">
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<div className="m-0 font-semibold text-base text-white">{TITLES[ping.type]}</div>
|
||||
|
||||
<div className="flex gap-1 items-center">
|
||||
{isShowSelectedSystem && (
|
||||
<>
|
||||
<SystemView systemId={selectedSystem} />
|
||||
<span className="pi pi-angle-double-right text-[10px] relative top-[1px] text-stone-400" />
|
||||
</>
|
||||
)}
|
||||
<SystemView systemId={ping.solar_system_id} />
|
||||
{isShowSelectedSystem && (
|
||||
<WdImgButton
|
||||
className={clsx(PrimeIcons.QUESTION_CIRCLE, 'ml-[2px] relative top-[-2px] !text-[10px]')}
|
||||
tooltip={{
|
||||
position: TooltipPosition.top,
|
||||
content: (
|
||||
<div className="flex flex-col gap-1">
|
||||
The settings for the route are taken from the Routes settings and can be configured
|
||||
through them.
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-end">
|
||||
<CharacterCardById className="" characterId={ping.character_eve_id} simpleMode />
|
||||
<TimeAgo timestamp={ping.inserted_at.toString()} className="text-stone-400 text-[11px]" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedSystem != null && <PingRoute />}
|
||||
|
||||
<p className="m-0 text-[13px] text-stone-200 min-h-[20px] pr-[16px]">{message.detail}</p>
|
||||
</div>
|
||||
|
||||
<WdImgButton
|
||||
className={clsx(PrimeIcons.TIMES, 'hover:text-red-400 mt-[3px]')}
|
||||
tooltip={CLOSE_TOOLTIP_PROPS}
|
||||
onClick={handleClickHide}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/*Button bar*/}
|
||||
<div className="flex justify-end items-center gap-2 h-0 relative top-[-8px]">
|
||||
<WdImgButton
|
||||
className={clsx('pi-compass', 'hover:text-red-400 mt-[3px]')}
|
||||
tooltip={NAVIGATE_TOOLTIP_PROPS}
|
||||
onClick={navigateTo}
|
||||
/>
|
||||
|
||||
{/*@ts-ignore*/}
|
||||
<div ref={cpRemoveBtnRef}>
|
||||
<WdImgButton
|
||||
className={clsx('pi-trash', 'text-red-400 hover:text-red-300')}
|
||||
tooltip={DELETE_TOOLTIP_PROPS}
|
||||
onClick={handleShowCP}
|
||||
/>
|
||||
</div>
|
||||
{/* TODO ADD solar system menu*/}
|
||||
{/*<WdImgButton*/}
|
||||
{/* className={clsx('pi-map-marker', 'hover:text-red-400 mt-[3px]')}*/}
|
||||
{/* tooltip={TOOLTIP_WAYPOINT_PROPS}*/}
|
||||
{/* onClick={handleClickHide}*/}
|
||||
{/*/>*/}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
></Toast>
|
||||
|
||||
<Button
|
||||
icon="pi pi-bell"
|
||||
severity="warning"
|
||||
aria-label="Notification"
|
||||
size="small"
|
||||
className="w-[33px] h-[33px]"
|
||||
outlined
|
||||
onClick={handleClickShow}
|
||||
disabled={isShow}
|
||||
/>
|
||||
|
||||
<ConfirmPopup
|
||||
target={cpRemoveBtnRef.current}
|
||||
visible={cpRemoveVisible}
|
||||
onHide={handleHideCP}
|
||||
message="Are you sure you want to delete ping?"
|
||||
icon="pi pi-exclamation-triangle text-orange-400"
|
||||
accept={removePing}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './PingsInterface.tsx';
|
||||
@@ -0,0 +1,101 @@
|
||||
import { InputTextarea } from 'primereact/inputtextarea';
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { Button } from 'primereact/button';
|
||||
import { OutCommand } from '@/hooks/Mapper/types';
|
||||
import { PingType } from '@/hooks/Mapper/types/ping.ts';
|
||||
import { SystemView } from '@/hooks/Mapper/components/ui-kit';
|
||||
import clsx from 'clsx';
|
||||
|
||||
const PING_TITLES = {
|
||||
[PingType.Rally]: 'RALLY',
|
||||
[PingType.Alert]: 'ALERT',
|
||||
};
|
||||
|
||||
interface SystemPingDialogProps {
|
||||
systemId: string;
|
||||
type: PingType;
|
||||
visible: boolean;
|
||||
setVisible: (visible: boolean) => void;
|
||||
}
|
||||
|
||||
export const SystemPingDialog = ({ systemId, type, visible, setVisible }: SystemPingDialogProps) => {
|
||||
const { outCommand } = useMapRootState();
|
||||
|
||||
const [message, setMessage] = useState('');
|
||||
const inputRef = useRef<HTMLTextAreaElement>();
|
||||
|
||||
const ref = useRef({ message, outCommand, systemId, type });
|
||||
ref.current = { message, outCommand, systemId, type };
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
const { message, outCommand, systemId, type } = ref.current;
|
||||
|
||||
outCommand({
|
||||
type: OutCommand.addPing,
|
||||
data: {
|
||||
solar_system_id: systemId,
|
||||
type,
|
||||
message,
|
||||
},
|
||||
});
|
||||
setVisible(false);
|
||||
}, [setVisible]);
|
||||
|
||||
const onShow = useCallback(() => {
|
||||
inputRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
header={
|
||||
<div className="flex gap-1 text-[13px] items-center text-stone-300">
|
||||
<div>Ping:{` `}</div>
|
||||
<div
|
||||
className={clsx({
|
||||
['text-cyan-400']: type === PingType.Rally,
|
||||
})}
|
||||
>
|
||||
{PING_TITLES[type]}
|
||||
</div>
|
||||
<div className="text-[11px]">in</div> <SystemView systemId={systemId} className="relative top-[1px]" />
|
||||
</div>
|
||||
}
|
||||
visible={visible}
|
||||
draggable={false}
|
||||
style={{ width: '450px' }}
|
||||
onShow={onShow}
|
||||
onHide={() => {
|
||||
if (!visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
setVisible(false);
|
||||
}}
|
||||
>
|
||||
<form onSubmit={handleSave}>
|
||||
<div className="flex flex-col gap-3 px-2">
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-[11px]" htmlFor="username">
|
||||
Message
|
||||
</label>
|
||||
<InputTextarea
|
||||
// @ts-ignore
|
||||
ref={inputRef}
|
||||
autoResize
|
||||
rows={3}
|
||||
cols={30}
|
||||
value={message}
|
||||
onChange={e => setMessage(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 justify-end">
|
||||
<Button onClick={handleSave} size="small" severity="danger" label="Ping!"></Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './SystemPingDialog';
|
||||
@@ -2,3 +2,4 @@ export * from './Widget';
|
||||
export * from './SystemSettingsDialog';
|
||||
export * from './SystemCustomLabelDialog';
|
||||
export * from './SystemLinkSignatureDialog';
|
||||
export * from './PingsInterface';
|
||||
|
||||
@@ -62,7 +62,7 @@ export const DEFAULT_WIDGETS: WindowProps[] = [
|
||||
},
|
||||
{
|
||||
id: WidgetsIds.userRoutes,
|
||||
position: { x: 10, y: 530 },
|
||||
position: { x: 10, y: 10 },
|
||||
size: { width: 510, height: 200 },
|
||||
zIndex: 0,
|
||||
content: () => <WRoutesUser />,
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { CharacterTypeRaw, WithIsOwnCharacter } from '@/hooks/Mapper/types';
|
||||
|
||||
export const sortCharacters = (a: CharacterTypeRaw & WithIsOwnCharacter, b: CharacterTypeRaw & WithIsOwnCharacter) => {
|
||||
if (a.online === b.online) {
|
||||
return a.name.localeCompare(b.name);
|
||||
}
|
||||
|
||||
if (a.online !== b.online) {
|
||||
return a.online && !b.online ? -1 : 1;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import classes from './LocalCharactersItemTemplate.module.scss';
|
||||
import clsx from 'clsx';
|
||||
import { CharacterCard } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { CharItemProps } from '@/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/components';
|
||||
import { CharacterCard } from '@/hooks/Mapper/components/ui-kit';
|
||||
import clsx from 'clsx';
|
||||
import { VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
|
||||
import classes from './LocalCharactersItemTemplate.module.scss';
|
||||
|
||||
export type LocalCharactersItemTemplateProps = { showShipName: boolean } & CharItemProps &
|
||||
VirtualScrollerTemplateOptions;
|
||||
@@ -22,7 +22,7 @@ export const LocalCharactersItemTemplate = ({ showShipName, ...options }: LocalC
|
||||
)}
|
||||
style={{ height: `${options.props.itemSize}px` }}
|
||||
>
|
||||
<CharacterCard showShipName={showShipName} showTicker {...options} />
|
||||
<CharacterCard showShipName={showShipName} showTicker showShip {...options} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { createContext, forwardRef, useContext, useImperativeHandle, useState } from 'react';
|
||||
import React, { createContext, forwardRef, useContext } from 'react';
|
||||
import {
|
||||
RoutesImperativeHandle,
|
||||
RoutesProviderInnerProps,
|
||||
@@ -15,17 +15,14 @@ const RoutesContext = createContext<RoutesProviderInnerProps>({
|
||||
data: {},
|
||||
});
|
||||
|
||||
export const RoutesProvider = forwardRef<RoutesImperativeHandle, MapProviderProps>(({ children, ...props }, ref) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
// INFO: this component have imperative handler but now it not using.
|
||||
export const RoutesProvider = forwardRef<RoutesImperativeHandle, MapProviderProps>(
|
||||
({ children, ...props } /*, ref*/) => {
|
||||
// useImperativeHandle(ref, () => ({}));
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
stopLoading() {
|
||||
setLoading(false);
|
||||
},
|
||||
}));
|
||||
|
||||
return <RoutesContext.Provider value={{ ...props, loading, setLoading }}>{children}</RoutesContext.Provider>;
|
||||
});
|
||||
return <RoutesContext.Provider value={{ ...props /*, loading, setLoading*/ }}>{children}</RoutesContext.Provider>;
|
||||
},
|
||||
);
|
||||
RoutesProvider.displayName = 'RoutesProvider';
|
||||
|
||||
export const useRouteProvider = () => {
|
||||
|
||||
@@ -3,16 +3,15 @@ import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import {
|
||||
LayoutEventBlocker,
|
||||
LoadingWrapper,
|
||||
SystemViewStandalone,
|
||||
SystemView,
|
||||
TooltipPosition,
|
||||
WdCheckbox,
|
||||
WdImgButton,
|
||||
} from '@/hooks/Mapper/components/ui-kit';
|
||||
import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts';
|
||||
|
||||
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';
|
||||
import { RoutesList } from './RoutesList';
|
||||
import clsx from 'clsx';
|
||||
import { Route } from '@/hooks/Mapper/types/routes.ts';
|
||||
@@ -42,24 +41,13 @@ export const RoutesWidgetContent = () => {
|
||||
const {
|
||||
data: { selectedSystems, systems, isSubscriptionActive },
|
||||
} = useMapRootState();
|
||||
const { hubs = [], routesList, isRestricted } = useRouteProvider();
|
||||
const { hubs = [], routesList, isRestricted, loading } = useRouteProvider();
|
||||
|
||||
const [systemId] = selectedSystems;
|
||||
|
||||
const { loading } = useLoadRoutes();
|
||||
|
||||
const { systems: systemStatics, loadSystems, lastUpdateKey } = useLoadSystemStatic({ systems: hubs ?? [] });
|
||||
const { systems: systemStatics, loadSystems } = useLoadSystemStatic({ systems: hubs ?? [] });
|
||||
const { open, ...systemCtxProps } = useContextMenuSystemInfoHandlers();
|
||||
|
||||
const preparedHubs = useMemo(() => {
|
||||
return hubs.map(x => {
|
||||
const sys = getSystemById(systems, x.toString());
|
||||
|
||||
return { ...systemStatics.get(parseInt(x))!, ...(sys && { customName: sys.name ?? '' }) };
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [hubs, systems, systemStatics, lastUpdateKey]);
|
||||
|
||||
const preparedRoutes: Route[] = useMemo(() => {
|
||||
return (
|
||||
routesList?.routes
|
||||
@@ -125,9 +113,7 @@ export const RoutesWidgetContent = () => {
|
||||
<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)!;
|
||||
|
||||
// TODO do not delte this console log
|
||||
// TODO do not delete this console log
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('JOipP', `Check sys [${route.destination}]:`, sys);
|
||||
|
||||
@@ -144,12 +130,12 @@ export const RoutesWidgetContent = () => {
|
||||
}}
|
||||
/>
|
||||
|
||||
<SystemViewStandalone
|
||||
key={route.destination}
|
||||
<SystemView
|
||||
systemId={route.destination.toString()}
|
||||
className={clsx('select-none text-center cursor-context-menu')}
|
||||
hideRegion
|
||||
compact
|
||||
{...sys}
|
||||
showCustomName
|
||||
/>
|
||||
</div>
|
||||
<div className="text-right pl-1">{route.has_connection ? route.systems?.length ?? 2 : ''}</div>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useRouteProvider } from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/RoutesProvider.tsx';
|
||||
import { RoutesType } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
import { LoadRoutesCommand } from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/types.ts';
|
||||
import { RoutesList } from '@/hooks/Mapper/types/routes.ts';
|
||||
|
||||
function usePrevious<T>(value: T): T | undefined {
|
||||
const ref = useRef<T>();
|
||||
@@ -13,8 +14,22 @@ function usePrevious<T>(value: T): T | undefined {
|
||||
return ref.current;
|
||||
}
|
||||
|
||||
export const useLoadRoutes = () => {
|
||||
const { data: routesSettings, loadRoutesCommand, hubs, routesList, loading, setLoading } = useRouteProvider();
|
||||
type UseLoadRoutesProps = {
|
||||
loadRoutesCommand: LoadRoutesCommand;
|
||||
hubs: string[];
|
||||
routesList: RoutesList | undefined;
|
||||
data: RoutesType;
|
||||
deps?: unknown[];
|
||||
};
|
||||
|
||||
export const useLoadRoutes = ({
|
||||
data: routesSettings,
|
||||
loadRoutesCommand,
|
||||
hubs,
|
||||
routesList,
|
||||
deps = [],
|
||||
}: UseLoadRoutesProps) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const {
|
||||
data: { selectedSystems, systems, connections },
|
||||
@@ -55,7 +70,8 @@ export const useLoadRoutes = () => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
.map(x => routesSettings[x]),
|
||||
...deps,
|
||||
]);
|
||||
|
||||
return { loading, loadRoutes };
|
||||
return { loading, loadRoutes, setLoading };
|
||||
};
|
||||
|
||||
@@ -10,17 +10,14 @@ export type RoutesWidgetProps = {
|
||||
update: (d: RoutesType) => void;
|
||||
hubs: string[];
|
||||
routesList: RoutesList | undefined;
|
||||
loading: boolean;
|
||||
|
||||
loadRoutesCommand: LoadRoutesCommand;
|
||||
addHubCommand: AddHubCommand;
|
||||
toggleHubCommand: ToggleHubCommand;
|
||||
isRestricted?: boolean;
|
||||
};
|
||||
|
||||
export type RoutesProviderInnerProps = RoutesWidgetProps & {
|
||||
loading: boolean;
|
||||
setLoading(loading: boolean): void;
|
||||
};
|
||||
export type RoutesProviderInnerProps = RoutesWidgetProps;
|
||||
|
||||
export type RoutesImperativeHandle = {
|
||||
stopLoading: () => void;
|
||||
|
||||
@@ -10,7 +10,7 @@ export interface SignatureViewProps {
|
||||
export const SignatureView = ({ signature, showCharacterPortrait = false }: SignatureViewProps) => {
|
||||
const isWormhole = signature?.group === SignatureGroup.Wormhole;
|
||||
const hasCharacterInfo = showCharacterPortrait && signature.character_eve_id;
|
||||
const groupDisplay = isWormhole ? SignatureGroup.Wormhole : (signature?.group ?? SignatureGroup.CosmicSignature);
|
||||
const groupDisplay = isWormhole ? SignatureGroup.Wormhole : signature?.group ?? SignatureGroup.CosmicSignature;
|
||||
const characterName = signature.character_name || 'Unknown character';
|
||||
|
||||
return (
|
||||
|
||||
@@ -19,7 +19,7 @@ export type HeaderProps = {
|
||||
lazyDeleteValue: boolean;
|
||||
onLazyDeleteChange: (checked: boolean) => void;
|
||||
pendingCount: number;
|
||||
pendingTimeRemaining?: number; // Time remaining in ms
|
||||
undoCountdown?: number;
|
||||
onUndoClick: () => void;
|
||||
onSettingsClick: () => void;
|
||||
};
|
||||
@@ -29,7 +29,7 @@ export const SystemSignaturesHeader = ({
|
||||
lazyDeleteValue,
|
||||
onLazyDeleteChange,
|
||||
pendingCount,
|
||||
pendingTimeRemaining,
|
||||
undoCountdown,
|
||||
onUndoClick,
|
||||
onSettingsClick,
|
||||
}: HeaderProps) => {
|
||||
@@ -43,13 +43,6 @@ export const SystemSignaturesHeader = ({
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const isCompact = useMaxWidth(containerRef, COMPACT_MAX_WIDTH);
|
||||
|
||||
// Format time remaining as seconds
|
||||
const formatTimeRemaining = () => {
|
||||
if (!pendingTimeRemaining) return '';
|
||||
const seconds = Math.ceil(pendingTimeRemaining / 1000);
|
||||
return ` (${seconds}s remaining)`;
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="w-full">
|
||||
<div className="flex justify-between items-center text-xs w-full h-full">
|
||||
@@ -78,7 +71,9 @@ export const SystemSignaturesHeader = ({
|
||||
<WdImgButton
|
||||
className={PrimeIcons.UNDO}
|
||||
style={{ color: 'red' }}
|
||||
tooltip={{ content: `Undo pending changes (${pendingCount})${formatTimeRemaining()}` }}
|
||||
tooltip={{
|
||||
content: `Undo pending deletions (${pendingCount})${undoCountdown && undoCountdown > 0 ? ` — ${undoCountdown}s left` : ''}`,
|
||||
}}
|
||||
onClick={onUndoClick}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1,99 +1,156 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useCallback, useState, useEffect, useRef, useMemo } from 'react';
|
||||
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
|
||||
import { SystemSignaturesContent } from './SystemSignaturesContent';
|
||||
import { SystemSignatureSettingsDialog } from './SystemSignatureSettingsDialog';
|
||||
import { ExtendedSystemSignature, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useHotkey } from '@/hooks/Mapper/hooks';
|
||||
import { SystemSignaturesHeader } from './SystemSignatureHeader';
|
||||
import useLocalStorageState from 'use-local-storage-state';
|
||||
import { useHotkey } from '@/hooks/Mapper/hooks/useHotkey';
|
||||
import {
|
||||
SETTINGS_KEYS,
|
||||
SETTINGS_VALUES,
|
||||
SIGNATURE_DELETION_TIMEOUTS,
|
||||
SIGNATURE_SETTING_STORE_KEY,
|
||||
SIGNATURE_WINDOW_ID,
|
||||
SIGNATURES_DELETION_TIMING,
|
||||
SignatureSettingsType,
|
||||
SIGNATURES_DELETION_TIMING,
|
||||
SIGNATURE_DELETION_TIMEOUTS,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||
import { calculateTimeRemaining } from './helpers';
|
||||
import { OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers';
|
||||
|
||||
export const SystemSignatures = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [sigCount, setSigCount] = useState<number>(0);
|
||||
const [pendingSigs, setPendingSigs] = useState<SystemSignature[]>([]);
|
||||
const [pendingTimeRemaining, setPendingTimeRemaining] = useState<number | undefined>();
|
||||
const undoPendingFnRef = useRef<() => void>(() => {});
|
||||
/**
|
||||
* Custom hook for managing pending signature deletions and undo countdown.
|
||||
*/
|
||||
function useSignatureUndo(
|
||||
systemId: string | undefined,
|
||||
settings: SignatureSettingsType,
|
||||
outCommand: OutCommandHandler,
|
||||
) {
|
||||
const [countdown, setCountdown] = useState<number>(0);
|
||||
const [pendingIds, setPendingIds] = useState<Set<string>>(new Set());
|
||||
const intervalRef = useRef<number | null>(null);
|
||||
|
||||
const {
|
||||
data: { selectedSystems },
|
||||
} = useMapRootState();
|
||||
|
||||
const [currentSettings, setCurrentSettings] = useLocalStorageState(SIGNATURE_SETTING_STORE_KEY, {
|
||||
defaultValue: SETTINGS_VALUES,
|
||||
});
|
||||
|
||||
const handleSigCountChange = useCallback((count: number) => {
|
||||
setSigCount(count);
|
||||
const addDeleted = useCallback((ids: string[]) => {
|
||||
setPendingIds(prev => {
|
||||
const next = new Set(prev);
|
||||
ids.forEach(id => next.add(id));
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const [systemId] = selectedSystems;
|
||||
const isNotSelectedSystem = selectedSystems.length !== 1;
|
||||
|
||||
const handleSettingsChange = useCallback((newSettings: SignatureSettingsType) => {
|
||||
setCurrentSettings(newSettings);
|
||||
setVisible(false);
|
||||
}, []);
|
||||
|
||||
const handleLazyDeleteChange = useCallback((value: boolean) => {
|
||||
setCurrentSettings(prev => ({ ...prev, [SETTINGS_KEYS.LAZY_DELETE_SIGNATURES]: value }));
|
||||
}, []);
|
||||
|
||||
useHotkey(true, ['z'], event => {
|
||||
if (pendingSigs.length > 0) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
undoPendingFnRef.current();
|
||||
setPendingSigs([]);
|
||||
setPendingTimeRemaining(undefined);
|
||||
}
|
||||
});
|
||||
|
||||
const handleUndoClick = useCallback(() => {
|
||||
undoPendingFnRef.current();
|
||||
setPendingSigs([]);
|
||||
setPendingTimeRemaining(undefined);
|
||||
}, []);
|
||||
|
||||
const handleSettingsButtonClick = useCallback(() => {
|
||||
setVisible(true);
|
||||
}, []);
|
||||
|
||||
const handlePendingChange = useCallback(
|
||||
(pending: React.MutableRefObject<Record<string, ExtendedSystemSignature>>, newUndo: () => void) => {
|
||||
setPendingSigs(() => {
|
||||
return Object.values(pending.current).filter(sig => sig.pendingDeletion);
|
||||
});
|
||||
undoPendingFnRef.current = newUndo;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
// Calculate the minimum time remaining for any pending signature
|
||||
// kick off or clear countdown whenever pendingIds changes
|
||||
useEffect(() => {
|
||||
if (pendingSigs.length === 0) {
|
||||
setPendingTimeRemaining(undefined);
|
||||
// clear any existing timer
|
||||
if (intervalRef.current != null) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
|
||||
if (pendingIds.size === 0) {
|
||||
setCountdown(0);
|
||||
return;
|
||||
}
|
||||
|
||||
const calculate = () => {
|
||||
setPendingTimeRemaining(() => calculateTimeRemaining(pendingSigs));
|
||||
};
|
||||
// determine timeout from settings
|
||||
const timingKey = Number(settings[SETTINGS_KEYS.DELETION_TIMING] ?? SIGNATURES_DELETION_TIMING.DEFAULT);
|
||||
const timeoutMs =
|
||||
Number(SIGNATURE_DELETION_TIMEOUTS[timingKey as keyof typeof SIGNATURE_DELETION_TIMEOUTS]) || 10000;
|
||||
setCountdown(Math.ceil(timeoutMs / 1000));
|
||||
|
||||
calculate();
|
||||
const interval = setInterval(calculate, 1000);
|
||||
return () => clearInterval(interval);
|
||||
}, [pendingSigs]);
|
||||
// start new interval
|
||||
intervalRef.current = window.setInterval(() => {
|
||||
setCountdown(prev => {
|
||||
if (prev <= 1) {
|
||||
clearInterval(intervalRef.current!);
|
||||
intervalRef.current = null;
|
||||
setPendingIds(new Set());
|
||||
return 0;
|
||||
}
|
||||
return prev - 1;
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
if (intervalRef.current != null) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [pendingIds, settings[SETTINGS_KEYS.DELETION_TIMING]]);
|
||||
|
||||
// undo handler
|
||||
const handleUndo = useCallback(async () => {
|
||||
if (!systemId || pendingIds.size === 0) return;
|
||||
await outCommand({
|
||||
type: OutCommand.undoDeleteSignatures,
|
||||
data: { system_id: systemId, eve_ids: Array.from(pendingIds) },
|
||||
});
|
||||
setPendingIds(new Set());
|
||||
setCountdown(0);
|
||||
if (intervalRef.current != null) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
}, [systemId, pendingIds, outCommand]);
|
||||
|
||||
return {
|
||||
pendingIds,
|
||||
countdown,
|
||||
addDeleted,
|
||||
handleUndo,
|
||||
};
|
||||
}
|
||||
|
||||
export const SystemSignatures = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [sigCount, setSigCount] = useState(0);
|
||||
|
||||
const {
|
||||
data: { selectedSystems },
|
||||
outCommand,
|
||||
} = useMapRootState();
|
||||
|
||||
const [currentSettings, setCurrentSettings] = useLocalStorageState<SignatureSettingsType>(
|
||||
SIGNATURE_SETTING_STORE_KEY,
|
||||
{
|
||||
defaultValue: SETTINGS_VALUES,
|
||||
},
|
||||
);
|
||||
|
||||
const [systemId] = selectedSystems;
|
||||
const isSystemSelected = useMemo(() => selectedSystems.length === 1, [selectedSystems.length]);
|
||||
const { pendingIds, countdown, addDeleted, handleUndo } = useSignatureUndo(systemId, currentSettings, outCommand);
|
||||
|
||||
useHotkey(true, ['z', 'Z'], (event: KeyboardEvent) => {
|
||||
if (pendingIds.size > 0 && countdown > 0) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
handleUndo();
|
||||
}
|
||||
});
|
||||
|
||||
const handleCountChange = useCallback((count: number) => {
|
||||
setSigCount(count);
|
||||
}, []);
|
||||
|
||||
const handleSettingsSave = useCallback(
|
||||
(newSettings: SignatureSettingsType) => {
|
||||
setCurrentSettings(newSettings);
|
||||
setVisible(false);
|
||||
},
|
||||
[setCurrentSettings],
|
||||
);
|
||||
|
||||
const handleLazyDeleteToggle = useCallback(
|
||||
(value: boolean) => {
|
||||
setCurrentSettings(prev => ({
|
||||
...prev,
|
||||
[SETTINGS_KEYS.LAZY_DELETE_SIGNATURES]: value,
|
||||
}));
|
||||
},
|
||||
[setCurrentSettings],
|
||||
);
|
||||
|
||||
const openSettings = useCallback(() => setVisible(true), []);
|
||||
|
||||
return (
|
||||
<Widget
|
||||
@@ -101,16 +158,16 @@ export const SystemSignatures = () => {
|
||||
<SystemSignaturesHeader
|
||||
sigCount={sigCount}
|
||||
lazyDeleteValue={currentSettings[SETTINGS_KEYS.LAZY_DELETE_SIGNATURES] as boolean}
|
||||
pendingCount={pendingSigs.length}
|
||||
pendingTimeRemaining={pendingTimeRemaining}
|
||||
onLazyDeleteChange={handleLazyDeleteChange}
|
||||
onUndoClick={handleUndoClick}
|
||||
onSettingsClick={handleSettingsButtonClick}
|
||||
pendingCount={pendingIds.size}
|
||||
undoCountdown={countdown}
|
||||
onLazyDeleteChange={handleLazyDeleteToggle}
|
||||
onUndoClick={handleUndo}
|
||||
onSettingsClick={openSettings}
|
||||
/>
|
||||
}
|
||||
windowId={SIGNATURE_WINDOW_ID}
|
||||
>
|
||||
{isNotSelectedSystem ? (
|
||||
{!isSystemSelected ? (
|
||||
<div className="w-full h-full flex justify-center items-center select-none text-center text-stone-400/80 text-sm">
|
||||
System is not selected
|
||||
</div>
|
||||
@@ -118,22 +175,17 @@ export const SystemSignatures = () => {
|
||||
<SystemSignaturesContent
|
||||
systemId={systemId}
|
||||
settings={currentSettings}
|
||||
deletionTiming={
|
||||
SIGNATURE_DELETION_TIMEOUTS[
|
||||
(currentSettings[SETTINGS_KEYS.DELETION_TIMING] as keyof typeof SIGNATURE_DELETION_TIMEOUTS) ||
|
||||
SIGNATURES_DELETION_TIMING.DEFAULT
|
||||
] as number
|
||||
}
|
||||
onLazyDeleteChange={handleLazyDeleteChange}
|
||||
onCountChange={handleSigCountChange}
|
||||
onPendingChange={handlePendingChange}
|
||||
onLazyDeleteChange={handleLazyDeleteToggle}
|
||||
onCountChange={handleCountChange}
|
||||
onSignatureDeleted={addDeleted}
|
||||
/>
|
||||
)}
|
||||
|
||||
{visible && (
|
||||
<SystemSignatureSettingsDialog
|
||||
settings={currentSettings}
|
||||
onCancel={() => setVisible(false)}
|
||||
onSave={handleSettingsChange}
|
||||
onSave={handleSettingsSave}
|
||||
/>
|
||||
)}
|
||||
</Widget>
|
||||
|
||||
@@ -57,12 +57,8 @@ interface SystemSignaturesContentProps {
|
||||
onSelect?: (signature: SystemSignature) => void;
|
||||
onLazyDeleteChange?: (value: boolean) => void;
|
||||
onCountChange?: (count: number) => void;
|
||||
onPendingChange?: (
|
||||
pending: React.MutableRefObject<Record<string, ExtendedSystemSignature>>,
|
||||
undo: () => void,
|
||||
) => void;
|
||||
deletionTiming?: number;
|
||||
filterSignature?: (signature: SystemSignature) => boolean;
|
||||
onSignatureDeleted?: (deletedIds: string[]) => void;
|
||||
}
|
||||
|
||||
export const SystemSignaturesContent = ({
|
||||
@@ -73,9 +69,8 @@ export const SystemSignaturesContent = ({
|
||||
onSelect,
|
||||
onLazyDeleteChange,
|
||||
onCountChange,
|
||||
onPendingChange,
|
||||
deletionTiming,
|
||||
filterSignature,
|
||||
onSignatureDeleted,
|
||||
}: SystemSignaturesContentProps) => {
|
||||
const [selectedSignatureForDialog, setSelectedSignatureForDialog] = useState<SystemSignature | null>(null);
|
||||
const [showSignatureSettings, setShowSignatureSettings] = useState(false);
|
||||
@@ -95,15 +90,21 @@ export const SystemSignaturesContent = ({
|
||||
{ defaultValue: SORT_DEFAULT_VALUES },
|
||||
);
|
||||
|
||||
const { signatures, selectedSignatures, setSelectedSignatures, handleDeleteSelected, handleSelectAll, handlePaste } =
|
||||
useSystemSignaturesData({
|
||||
systemId,
|
||||
settings,
|
||||
onCountChange,
|
||||
onPendingChange,
|
||||
onLazyDeleteChange,
|
||||
deletionTiming,
|
||||
});
|
||||
const {
|
||||
signatures,
|
||||
selectedSignatures,
|
||||
setSelectedSignatures,
|
||||
handleDeleteSelected,
|
||||
handleSelectAll,
|
||||
handlePaste,
|
||||
hasUnsupportedLanguage,
|
||||
} = useSystemSignaturesData({
|
||||
systemId,
|
||||
settings,
|
||||
onCountChange,
|
||||
onLazyDeleteChange,
|
||||
onSignatureDeleted,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (selectable) return;
|
||||
@@ -125,6 +126,10 @@ export const SystemSignaturesContent = ({
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (onSignatureDeleted && selectedSignatures.length > 0) {
|
||||
const deletedIds = selectedSignatures.map(s => s.eve_id);
|
||||
onSignatureDeleted(deletedIds);
|
||||
}
|
||||
handleDeleteSelected();
|
||||
});
|
||||
|
||||
@@ -155,7 +160,7 @@ export const SystemSignaturesContent = ({
|
||||
(e: { value: SystemSignature[] }) => {
|
||||
selectable ? onSelect?.(e.value[0]) : setSelectedSignatures(e.value as ExtendedSystemSignature[]);
|
||||
},
|
||||
[selectable],
|
||||
[onSelect, selectable, setSelectedSignatures],
|
||||
);
|
||||
|
||||
const { showDescriptionColumn, showUpdatedColumn, showCharacterColumn, showCharacterPortrait } = useMemo(
|
||||
@@ -188,7 +193,11 @@ export const SystemSignaturesContent = ({
|
||||
x => GROUPS_LIST.includes(x as SignatureGroup) && settings[x as SETTINGS_KEYS],
|
||||
);
|
||||
|
||||
return enabledGroups.includes(getGroupIdByRawGroup(sig.group));
|
||||
const mappedGroup = getGroupIdByRawGroup(sig.group);
|
||||
if (!mappedGroup) {
|
||||
return true; // If we can't determine the group, still show it
|
||||
}
|
||||
return enabledGroups.includes(mappedGroup);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -236,113 +245,121 @@ export const SystemSignaturesContent = ({
|
||||
No signatures
|
||||
</div>
|
||||
) : (
|
||||
<DataTable
|
||||
value={filteredSignatures}
|
||||
size="small"
|
||||
selectionMode="multiple"
|
||||
selection={selectedSignatures}
|
||||
metaKeySelection
|
||||
onSelectionChange={handleSelectSignatures}
|
||||
dataKey="eve_id"
|
||||
className="w-full select-none"
|
||||
resizableColumns={false}
|
||||
rowHover
|
||||
selectAll
|
||||
onRowDoubleClick={handleRowClick}
|
||||
sortField={sortSettings.sortField}
|
||||
sortOrder={sortSettings.sortOrder}
|
||||
onSort={handleSortSettings}
|
||||
onRowMouseEnter={onRowMouseEnter}
|
||||
onRowMouseLeave={onRowMouseLeave}
|
||||
// @ts-ignore
|
||||
rowClassName={getRowClassName}
|
||||
>
|
||||
<Column
|
||||
field="icon"
|
||||
header=""
|
||||
body={renderColIcon}
|
||||
bodyClassName="p-0 px-1"
|
||||
style={{ maxWidth: 26, minWidth: 26, width: 26 }}
|
||||
/>
|
||||
<Column
|
||||
field="eve_id"
|
||||
header="Id"
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
style={{ maxWidth: 72, minWidth: 72, width: 72 }}
|
||||
sortable
|
||||
/>
|
||||
<Column
|
||||
field="group"
|
||||
header="Group"
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
style={{ maxWidth: 110, minWidth: 110, width: 110 }}
|
||||
body={sig => sig.group ?? ''}
|
||||
hidden={isCompact}
|
||||
sortable
|
||||
/>
|
||||
<Column
|
||||
field="info"
|
||||
header="Info"
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
style={{ maxWidth: nameColumnWidth }}
|
||||
hidden={isCompact || isMedium}
|
||||
body={renderInfoColumn}
|
||||
/>
|
||||
{showDescriptionColumn && (
|
||||
<Column
|
||||
field="description"
|
||||
header="Description"
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
hidden={isCompact}
|
||||
body={renderDescription}
|
||||
sortable
|
||||
/>
|
||||
<>
|
||||
{hasUnsupportedLanguage && (
|
||||
<div className="w-full flex justify-center items-center text-amber-500 text-xs p-1 bg-amber-950/20 border-b border-amber-800/30">
|
||||
<i className={PrimeIcons.EXCLAMATION_TRIANGLE + ' mr-1'} />
|
||||
Non-English signatures detected. Some signatures may not display correctly. Double-click to edit signature details.
|
||||
</div>
|
||||
)}
|
||||
<Column
|
||||
field="inserted_at"
|
||||
header="Added"
|
||||
dataType="date"
|
||||
body={renderAddedTimeLeft}
|
||||
style={{ minWidth: 70, maxWidth: 80 }}
|
||||
bodyClassName="ssc-header text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
sortable
|
||||
/>
|
||||
{showUpdatedColumn && (
|
||||
<Column
|
||||
field="updated_at"
|
||||
header="Updated"
|
||||
dataType="date"
|
||||
body={renderUpdatedTimeLeft}
|
||||
style={{ minWidth: 70, maxWidth: 80 }}
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
sortable
|
||||
/>
|
||||
)}
|
||||
|
||||
{showCharacterColumn && (
|
||||
<Column
|
||||
field="character_name"
|
||||
header="Character"
|
||||
bodyClassName="w-[70px] text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
sortable
|
||||
></Column>
|
||||
)}
|
||||
|
||||
{!selectable && (
|
||||
<DataTable
|
||||
value={filteredSignatures}
|
||||
size="small"
|
||||
selectionMode="multiple"
|
||||
selection={selectedSignatures}
|
||||
metaKeySelection
|
||||
onSelectionChange={handleSelectSignatures}
|
||||
dataKey="eve_id"
|
||||
className="w-full select-none"
|
||||
resizableColumns={false}
|
||||
rowHover
|
||||
selectAll
|
||||
onRowDoubleClick={handleRowClick}
|
||||
sortField={sortSettings.sortField}
|
||||
sortOrder={sortSettings.sortOrder}
|
||||
onSort={handleSortSettings}
|
||||
onRowMouseEnter={onRowMouseEnter}
|
||||
onRowMouseLeave={onRowMouseLeave}
|
||||
// @ts-ignore
|
||||
rowClassName={getRowClassName}
|
||||
>
|
||||
<Column
|
||||
field="icon"
|
||||
header=""
|
||||
body={() => (
|
||||
<div className="flex justify-end items-center gap-2 mr-[4px]">
|
||||
<WdTooltipWrapper content="Double-click a row to edit signature">
|
||||
<span className={PrimeIcons.PENCIL + ' text-[10px]'} />
|
||||
</WdTooltipWrapper>
|
||||
</div>
|
||||
)}
|
||||
body={renderColIcon}
|
||||
bodyClassName="p-0 px-1"
|
||||
style={{ maxWidth: 26, minWidth: 26, width: 26 }}
|
||||
bodyClassName="p-0 pl-1 pr-2"
|
||||
/>
|
||||
)}
|
||||
</DataTable>
|
||||
<Column
|
||||
field="eve_id"
|
||||
header="Id"
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
style={{ maxWidth: 72, minWidth: 72, width: 72 }}
|
||||
sortable
|
||||
/>
|
||||
<Column
|
||||
field="group"
|
||||
header="Group"
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
style={{ maxWidth: 110, minWidth: 110, width: 110 }}
|
||||
body={sig => sig.group ?? ''}
|
||||
hidden={isCompact}
|
||||
sortable
|
||||
/>
|
||||
<Column
|
||||
field="info"
|
||||
header="Info"
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
style={{ maxWidth: nameColumnWidth }}
|
||||
hidden={isCompact || isMedium}
|
||||
body={renderInfoColumn}
|
||||
/>
|
||||
{showDescriptionColumn && (
|
||||
<Column
|
||||
field="description"
|
||||
header="Description"
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
hidden={isCompact}
|
||||
body={renderDescription}
|
||||
sortable
|
||||
/>
|
||||
)}
|
||||
<Column
|
||||
field="inserted_at"
|
||||
header="Added"
|
||||
dataType="date"
|
||||
body={renderAddedTimeLeft}
|
||||
style={{ minWidth: 70, maxWidth: 80 }}
|
||||
bodyClassName="ssc-header text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
sortable
|
||||
/>
|
||||
{showUpdatedColumn && (
|
||||
<Column
|
||||
field="updated_at"
|
||||
header="Updated"
|
||||
dataType="date"
|
||||
body={renderUpdatedTimeLeft}
|
||||
style={{ minWidth: 70, maxWidth: 80 }}
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
sortable
|
||||
/>
|
||||
)}
|
||||
|
||||
{showCharacterColumn && (
|
||||
<Column
|
||||
field="character_name"
|
||||
header="Character"
|
||||
bodyClassName="w-[70px] text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
sortable
|
||||
></Column>
|
||||
)}
|
||||
|
||||
{!selectable && (
|
||||
<Column
|
||||
header=""
|
||||
body={() => (
|
||||
<div className="flex justify-end items-center gap-2 mr-[4px]">
|
||||
<WdTooltipWrapper content="Double-click a row to edit signature">
|
||||
<span className={PrimeIcons.PENCIL + ' text-[10px]'} />
|
||||
</WdTooltipWrapper>
|
||||
</div>
|
||||
)}
|
||||
style={{ maxWidth: 26, minWidth: 26, width: 26 }}
|
||||
bodyClassName="p-0 pl-1 pr-2"
|
||||
/>
|
||||
)}
|
||||
</DataTable>
|
||||
</>
|
||||
)}
|
||||
|
||||
<WdTooltip
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import {
|
||||
GroupType,
|
||||
SignatureGroup,
|
||||
SignatureGroupDE,
|
||||
SignatureGroupENG,
|
||||
SignatureGroupFR,
|
||||
SignatureGroupRU,
|
||||
SignatureKind,
|
||||
SignatureKindDE,
|
||||
SignatureKindENG,
|
||||
SignatureKindFR,
|
||||
SignatureKindRU,
|
||||
} from '@/hooks/Mapper/types';
|
||||
|
||||
@@ -40,46 +44,58 @@ export const GROUPS: Record<SignatureGroup, GroupType> = {
|
||||
[SignatureGroup.CosmicSignature]: { id: SignatureGroup.CosmicSignature, icon: '/icons/x_close14.png', w: 9, h: 9 },
|
||||
};
|
||||
|
||||
export const MAPPING_GROUP_TO_ENG = {
|
||||
// ENGLISH
|
||||
[SignatureGroupENG.GasSite]: SignatureGroup.GasSite,
|
||||
[SignatureGroupENG.RelicSite]: SignatureGroup.RelicSite,
|
||||
[SignatureGroupENG.DataSite]: SignatureGroup.DataSite,
|
||||
[SignatureGroupENG.OreSite]: SignatureGroup.OreSite,
|
||||
[SignatureGroupENG.CombatSite]: SignatureGroup.CombatSite,
|
||||
[SignatureGroupENG.Wormhole]: SignatureGroup.Wormhole,
|
||||
[SignatureGroupENG.CosmicSignature]: SignatureGroup.CosmicSignature,
|
||||
|
||||
// RUSSIAN
|
||||
[SignatureGroupRU.GasSite]: SignatureGroup.GasSite,
|
||||
[SignatureGroupRU.RelicSite]: SignatureGroup.RelicSite,
|
||||
[SignatureGroupRU.DataSite]: SignatureGroup.DataSite,
|
||||
[SignatureGroupRU.OreSite]: SignatureGroup.OreSite,
|
||||
[SignatureGroupRU.CombatSite]: SignatureGroup.CombatSite,
|
||||
[SignatureGroupRU.Wormhole]: SignatureGroup.Wormhole,
|
||||
[SignatureGroupRU.CosmicSignature]: SignatureGroup.CosmicSignature,
|
||||
export const LANGUAGE_GROUP_MAPPINGS = {
|
||||
EN: {
|
||||
[SignatureGroupENG.GasSite]: SignatureGroup.GasSite,
|
||||
[SignatureGroupENG.RelicSite]: SignatureGroup.RelicSite,
|
||||
[SignatureGroupENG.DataSite]: SignatureGroup.DataSite,
|
||||
[SignatureGroupENG.OreSite]: SignatureGroup.OreSite,
|
||||
[SignatureGroupENG.CombatSite]: SignatureGroup.CombatSite,
|
||||
[SignatureGroupENG.Wormhole]: SignatureGroup.Wormhole,
|
||||
[SignatureGroupENG.CosmicSignature]: SignatureGroup.CosmicSignature,
|
||||
},
|
||||
RU: {
|
||||
[SignatureGroupRU.GasSite]: SignatureGroup.GasSite,
|
||||
[SignatureGroupRU.RelicSite]: SignatureGroup.RelicSite,
|
||||
[SignatureGroupRU.DataSite]: SignatureGroup.DataSite,
|
||||
[SignatureGroupRU.OreSite]: SignatureGroup.OreSite,
|
||||
[SignatureGroupRU.CombatSite]: SignatureGroup.CombatSite,
|
||||
[SignatureGroupRU.Wormhole]: SignatureGroup.Wormhole,
|
||||
[SignatureGroupRU.CosmicSignature]: SignatureGroup.CosmicSignature,
|
||||
},
|
||||
FR: {
|
||||
[SignatureGroupFR.GasSite]: SignatureGroup.GasSite,
|
||||
[SignatureGroupFR.RelicSite]: SignatureGroup.RelicSite,
|
||||
[SignatureGroupFR.DataSite]: SignatureGroup.DataSite,
|
||||
[SignatureGroupFR.OreSite]: SignatureGroup.OreSite,
|
||||
[SignatureGroupFR.CombatSite]: SignatureGroup.CombatSite,
|
||||
[SignatureGroupFR.Wormhole]: SignatureGroup.Wormhole,
|
||||
[SignatureGroupFR.CosmicSignature]: SignatureGroup.CosmicSignature,
|
||||
},
|
||||
DE: {
|
||||
[SignatureGroupDE.GasSite]: SignatureGroup.GasSite,
|
||||
[SignatureGroupDE.RelicSite]: SignatureGroup.RelicSite,
|
||||
[SignatureGroupDE.DataSite]: SignatureGroup.DataSite,
|
||||
[SignatureGroupDE.OreSite]: SignatureGroup.OreSite,
|
||||
[SignatureGroupDE.CombatSite]: SignatureGroup.CombatSite,
|
||||
[SignatureGroupDE.Wormhole]: SignatureGroup.Wormhole,
|
||||
[SignatureGroupDE.CosmicSignature]: SignatureGroup.CosmicSignature,
|
||||
},
|
||||
};
|
||||
|
||||
export const MAPPING_TYPE_TO_ENG = {
|
||||
// ENGLISH
|
||||
[SignatureKindENG.CosmicSignature]: SignatureKind.CosmicSignature,
|
||||
[SignatureKindENG.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
|
||||
[SignatureKindENG.Structure]: SignatureKind.Structure,
|
||||
[SignatureKindENG.Ship]: SignatureKind.Ship,
|
||||
[SignatureKindENG.Deployable]: SignatureKind.Deployable,
|
||||
[SignatureKindENG.Drone]: SignatureKind.Drone,
|
||||
// Flatten the structure for backward compatibility
|
||||
export const MAPPING_GROUP_TO_ENG: Record<string, SignatureGroup> = (() => {
|
||||
const flattened: Record<string, SignatureGroup> = {};
|
||||
for (const [, mappings] of Object.entries(LANGUAGE_GROUP_MAPPINGS)) {
|
||||
Object.assign(flattened, mappings);
|
||||
}
|
||||
return flattened;
|
||||
})();
|
||||
|
||||
// RUSSIAN
|
||||
[SignatureKindRU.CosmicSignature]: SignatureKind.CosmicSignature,
|
||||
[SignatureKindRU.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
|
||||
[SignatureKindRU.Structure]: SignatureKind.Structure,
|
||||
[SignatureKindRU.Ship]: SignatureKind.Ship,
|
||||
[SignatureKindRU.Deployable]: SignatureKind.Deployable,
|
||||
[SignatureKindRU.Drone]: SignatureKind.Drone,
|
||||
export const getGroupIdByRawGroup = (val: string): SignatureGroup | undefined => {
|
||||
return MAPPING_GROUP_TO_ENG[val] || undefined;
|
||||
};
|
||||
|
||||
export const getGroupIdByRawGroup = (val: string) => MAPPING_GROUP_TO_ENG[val as SignatureGroup];
|
||||
|
||||
export const SIGNATURE_WINDOW_ID = 'system_signatures_window';
|
||||
export const SIGNATURE_SETTING_STORE_KEY = 'wanderer_system_signature_settings_v6_5';
|
||||
|
||||
@@ -123,7 +139,7 @@ export type Setting = {
|
||||
name: string;
|
||||
type: SettingsTypes;
|
||||
isSeparator?: boolean;
|
||||
options?: { label: string; value: any }[];
|
||||
options?: { label: string; value: number | string | boolean }[];
|
||||
};
|
||||
|
||||
export enum SIGNATURES_DELETION_TIMING {
|
||||
@@ -208,3 +224,52 @@ export const SIGNATURE_DELETION_TIMEOUTS: SignatureDeletionTimingType = {
|
||||
[SIGNATURES_DELETION_TIMING.IMMEDIATE]: 0,
|
||||
[SIGNATURES_DELETION_TIMING.EXTENDED]: 30_000,
|
||||
};
|
||||
|
||||
// Replace the flat structure with a nested structure by language
|
||||
export const LANGUAGE_TYPE_MAPPINGS = {
|
||||
EN: {
|
||||
[SignatureKindENG.CosmicSignature]: SignatureKind.CosmicSignature,
|
||||
[SignatureKindENG.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
|
||||
[SignatureKindENG.Structure]: SignatureKind.Structure,
|
||||
[SignatureKindENG.Ship]: SignatureKind.Ship,
|
||||
[SignatureKindENG.Deployable]: SignatureKind.Deployable,
|
||||
[SignatureKindENG.Drone]: SignatureKind.Drone,
|
||||
[SignatureKindENG.Starbase]: SignatureKind.Starbase,
|
||||
},
|
||||
RU: {
|
||||
[SignatureKindRU.CosmicSignature]: SignatureKind.CosmicSignature,
|
||||
[SignatureKindRU.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
|
||||
[SignatureKindRU.Structure]: SignatureKind.Structure,
|
||||
[SignatureKindRU.Ship]: SignatureKind.Ship,
|
||||
[SignatureKindRU.Deployable]: SignatureKind.Deployable,
|
||||
[SignatureKindRU.Drone]: SignatureKind.Drone,
|
||||
[SignatureKindRU.Starbase]: SignatureKind.Starbase,
|
||||
},
|
||||
FR: {
|
||||
[SignatureKindFR.CosmicSignature]: SignatureKind.CosmicSignature,
|
||||
[SignatureKindFR.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
|
||||
[SignatureKindFR.Structure]: SignatureKind.Structure,
|
||||
[SignatureKindFR.Ship]: SignatureKind.Ship,
|
||||
[SignatureKindFR.Deployable]: SignatureKind.Deployable,
|
||||
[SignatureKindFR.Drone]: SignatureKind.Drone,
|
||||
[SignatureKindFR.Starbase]: SignatureKind.Starbase,
|
||||
},
|
||||
DE: {
|
||||
[SignatureKindDE.CosmicSignature]: SignatureKind.CosmicSignature,
|
||||
[SignatureKindDE.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
|
||||
[SignatureKindDE.Structure]: SignatureKind.Structure,
|
||||
[SignatureKindDE.Ship]: SignatureKind.Ship,
|
||||
[SignatureKindDE.Deployable]: SignatureKind.Deployable,
|
||||
[SignatureKindDE.Drone]: SignatureKind.Drone,
|
||||
[SignatureKindDE.Starbase]: SignatureKind.Starbase,
|
||||
},
|
||||
};
|
||||
|
||||
// Flatten the structure for backward compatibility
|
||||
export const MAPPING_TYPE_TO_ENG: Record<string, SignatureKind> = (() => {
|
||||
const flattened: Record<string, SignatureKind> = {};
|
||||
for (const [, mappings] of Object.entries(LANGUAGE_TYPE_MAPPINGS)) {
|
||||
Object.assign(flattened, mappings);
|
||||
}
|
||||
return flattened;
|
||||
})();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { GROUPS_LIST } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants';
|
||||
import { SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { getState } from './getState';
|
||||
|
||||
/**
|
||||
@@ -22,6 +22,7 @@ export const getActualSigs = (
|
||||
|
||||
oldSignatures.forEach(oldSig => {
|
||||
const newSig = newSignatures.find(s => s.eve_id === oldSig.eve_id);
|
||||
|
||||
if (newSig) {
|
||||
const needUpgrade = getState(GROUPS_LIST, newSig) > getState(GROUPS_LIST, oldSig);
|
||||
const mergedSig = { ...oldSig };
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { UNKNOWN_SIGNATURE_NAME } from '@/hooks/Mapper/helpers';
|
||||
import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
|
||||
|
||||
export const getState = (_: string[], newSig: SystemSignature) => {
|
||||
let state = -1;
|
||||
if (!newSig.group) {
|
||||
if (!newSig.group || newSig.group === SignatureGroup.CosmicSignature) {
|
||||
state = 0;
|
||||
} else if (!newSig.name || newSig.name === '') {
|
||||
} else if (!newSig.name || newSig.name === '' || newSig.name === UNKNOWN_SIGNATURE_NAME) {
|
||||
state = 1;
|
||||
} else if (newSig.name !== '') {
|
||||
state = 2;
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
import { useCallback, useRef, useEffect } from 'react';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
|
||||
import { prepareUpdatePayload, scheduleLazyTimers } from '../helpers';
|
||||
import { prepareUpdatePayload } from '../helpers';
|
||||
import { UsePendingDeletionParams } from './types';
|
||||
import { FINAL_DURATION_MS } from '../constants';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { ExtendedSystemSignature } from '@/hooks/Mapper/types';
|
||||
|
||||
export function usePendingDeletions({
|
||||
systemId,
|
||||
setSignatures,
|
||||
deletionTiming,
|
||||
onPendingChange,
|
||||
}: UsePendingDeletionParams) {
|
||||
}: Omit<UsePendingDeletionParams, 'deletionTiming'>) {
|
||||
const { outCommand } = useMapRootState();
|
||||
const pendingDeletionMapRef = useRef<Record<string, ExtendedSystemSignature>>({});
|
||||
|
||||
// Use the provided deletion timing or fall back to the default
|
||||
const finalDuration = deletionTiming !== undefined ? deletionTiming : FINAL_DURATION_MS;
|
||||
|
||||
const processRemovedSignatures = useCallback(
|
||||
async (
|
||||
removed: ExtendedSystemSignature[],
|
||||
@@ -25,63 +20,15 @@ export function usePendingDeletions({
|
||||
updated: ExtendedSystemSignature[],
|
||||
) => {
|
||||
if (!removed.length) return;
|
||||
|
||||
// If deletion timing is 0, immediately delete without pending state
|
||||
if (finalDuration === 0) {
|
||||
await outCommand({
|
||||
type: OutCommand.updateSignatures,
|
||||
data: prepareUpdatePayload(systemId, added, updated, removed),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const processedRemoved = removed.map(r => ({
|
||||
...r,
|
||||
pendingDeletion: true,
|
||||
pendingUntil: now + finalDuration,
|
||||
}));
|
||||
pendingDeletionMapRef.current = {
|
||||
...pendingDeletionMapRef.current,
|
||||
...processedRemoved.reduce((acc: any, sig) => {
|
||||
acc[sig.eve_id] = sig;
|
||||
return acc;
|
||||
}, {}),
|
||||
};
|
||||
|
||||
onPendingChange?.(pendingDeletionMapRef, clearPendingDeletions);
|
||||
|
||||
setSignatures(prev =>
|
||||
prev.map(sig => {
|
||||
if (processedRemoved.find(r => r.eve_id === sig.eve_id)) {
|
||||
return { ...sig, pendingDeletion: true, pendingUntil: now + finalDuration };
|
||||
}
|
||||
return sig;
|
||||
}),
|
||||
);
|
||||
|
||||
scheduleLazyTimers(
|
||||
processedRemoved,
|
||||
pendingDeletionMapRef,
|
||||
async sig => {
|
||||
await outCommand({
|
||||
type: OutCommand.updateSignatures,
|
||||
data: prepareUpdatePayload(systemId, [], [], [sig]),
|
||||
});
|
||||
delete pendingDeletionMapRef.current[sig.eve_id];
|
||||
setSignatures(prev => prev.filter(x => x.eve_id !== sig.eve_id));
|
||||
onPendingChange?.(pendingDeletionMapRef, clearPendingDeletions);
|
||||
},
|
||||
finalDuration,
|
||||
);
|
||||
await outCommand({
|
||||
type: OutCommand.updateSignatures,
|
||||
data: prepareUpdatePayload(systemId, added, updated, removed),
|
||||
});
|
||||
},
|
||||
[systemId, outCommand, finalDuration],
|
||||
[systemId, outCommand],
|
||||
);
|
||||
|
||||
const clearPendingDeletions = useCallback(() => {
|
||||
Object.values(pendingDeletionMapRef.current).forEach(({ finalTimeoutId }) => {
|
||||
clearTimeout(finalTimeoutId);
|
||||
});
|
||||
pendingDeletionMapRef.current = {};
|
||||
setSignatures(prev => prev.map(x => (x.pendingDeletion ? { ...x, pendingDeletion: false } : x)));
|
||||
onPendingChange?.(pendingDeletionMapRef, clearPendingDeletions);
|
||||
|
||||
@@ -18,16 +18,18 @@ export const useSystemSignaturesData = ({
|
||||
onCountChange,
|
||||
onPendingChange,
|
||||
onLazyDeleteChange,
|
||||
deletionTiming,
|
||||
}: UseSystemSignaturesDataProps) => {
|
||||
onSignatureDeleted,
|
||||
}: Omit<UseSystemSignaturesDataProps, 'deletionTiming'> & {
|
||||
onSignatureDeleted?: (deletedIds: string[]) => void;
|
||||
}) => {
|
||||
const { outCommand } = useMapRootState();
|
||||
const [signatures, setSignatures, signaturesRef] = useRefState<ExtendedSystemSignature[]>([]);
|
||||
const [selectedSignatures, setSelectedSignatures] = useState<ExtendedSystemSignature[]>([]);
|
||||
const [hasUnsupportedLanguage, setHasUnsupportedLanguage] = useState<boolean>(false);
|
||||
|
||||
const { pendingDeletionMapRef, processRemovedSignatures, clearPendingDeletions } = usePendingDeletions({
|
||||
systemId,
|
||||
setSignatures,
|
||||
deletionTiming,
|
||||
onPendingChange,
|
||||
});
|
||||
|
||||
@@ -42,6 +44,7 @@ export const useSystemSignaturesData = ({
|
||||
async (clipboardString: string) => {
|
||||
const lazyDeleteValue = settings[SETTINGS_KEYS.LAZY_DELETE_SIGNATURES] as boolean;
|
||||
|
||||
// Parse the incoming signatures
|
||||
const incomingSignatures = parseSignatures(
|
||||
clipboardString,
|
||||
Object.keys(settings).filter(skey => skey in SignatureKind),
|
||||
@@ -51,14 +54,30 @@ export const useSystemSignaturesData = ({
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if any signatures might be using unsupported languages
|
||||
// This is a basic heuristic: if we have signatures where the original group wasn't mapped
|
||||
const clipboardRows = clipboardString.split('\n').filter(row => row.trim() !== '');
|
||||
const detectedSignatureCount = clipboardRows.filter(row => row.match(/^[A-Z]{3}-\d{3}/)).length;
|
||||
|
||||
// If we detected valid IDs but got fewer parsed signatures, we might have language issues
|
||||
if (detectedSignatureCount > 0 && incomingSignatures.length < detectedSignatureCount) {
|
||||
setHasUnsupportedLanguage(true);
|
||||
} else {
|
||||
setHasUnsupportedLanguage(false);
|
||||
}
|
||||
|
||||
const currentNonPending = lazyDeleteValue
|
||||
? signaturesRef.current.filter(sig => !sig.pendingDeletion)
|
||||
: signaturesRef.current.filter(sig => !sig.pendingDeletion || !sig.pendingAddition);
|
||||
|
||||
const { added, updated, removed } = getActualSigs(currentNonPending, incomingSignatures, !lazyDeleteValue, true);
|
||||
const { added, updated, removed } = getActualSigs(currentNonPending, incomingSignatures, !lazyDeleteValue, false);
|
||||
|
||||
if (removed.length > 0) {
|
||||
await processRemovedSignatures(removed, added, updated);
|
||||
if (onSignatureDeleted) {
|
||||
const deletedIds = removed.map(sig => sig.eve_id);
|
||||
onSignatureDeleted(deletedIds);
|
||||
}
|
||||
}
|
||||
|
||||
if (updated.length !== 0 || added.length !== 0) {
|
||||
@@ -78,17 +97,16 @@ export const useSystemSignaturesData = ({
|
||||
onLazyDeleteChange?.(false);
|
||||
}
|
||||
},
|
||||
[settings, signaturesRef, processRemovedSignatures, outCommand, systemId, onLazyDeleteChange],
|
||||
[settings, signaturesRef, processRemovedSignatures, outCommand, systemId, onLazyDeleteChange, onSignatureDeleted],
|
||||
);
|
||||
|
||||
const handleDeleteSelected = useCallback(async () => {
|
||||
if (!selectedSignatures.length) return;
|
||||
const selectedIds = selectedSignatures.map(s => s.eve_id);
|
||||
const finalList = signatures.filter(s => !selectedIds.includes(s.eve_id));
|
||||
|
||||
await handleUpdateSignatures(finalList, false, true);
|
||||
setSelectedSignatures([]);
|
||||
}, [selectedSignatures, signatures]);
|
||||
}, [handleUpdateSignatures, selectedSignatures, signatures]);
|
||||
|
||||
const handleSelectAll = useCallback(() => {
|
||||
setSelectedSignatures(signatures);
|
||||
@@ -119,11 +137,12 @@ export const useSystemSignaturesData = ({
|
||||
}, [signatures]);
|
||||
|
||||
return {
|
||||
signatures,
|
||||
signatures: signatures.filter(sig => !sig.deleted),
|
||||
selectedSignatures,
|
||||
setSelectedSignatures,
|
||||
handleDeleteSelected,
|
||||
handleSelectAll,
|
||||
handlePaste,
|
||||
hasUnsupportedLanguage,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -2,7 +2,6 @@ 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';
|
||||
@@ -13,24 +12,11 @@ export const WRoutesPublic = () => {
|
||||
const {
|
||||
outCommand,
|
||||
storedSettings: { settingsRoutes, settingsRoutesUpdate },
|
||||
data: { hubs, routes },
|
||||
data: { hubs, routes, loadingPublicRoutes },
|
||||
} = 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)) {
|
||||
@@ -72,10 +58,10 @@ export const WRoutesPublic = () => {
|
||||
ref={ref}
|
||||
title="Routes"
|
||||
data={settingsRoutes}
|
||||
loading={loadingPublicRoutes}
|
||||
update={settingsRoutesUpdate}
|
||||
hubs={hubs}
|
||||
routesList={routes}
|
||||
loadRoutesCommand={loadRoutesCommand}
|
||||
addHubCommand={addHubCommand}
|
||||
toggleHubCommand={toggleHubCommand}
|
||||
/>
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { RoutesWidget } from '@/hooks/Mapper/components/mapInterface/widgets';
|
||||
import { useMapEventListener } from '@/hooks/Mapper/events';
|
||||
import { useLoadRoutes } from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/hooks';
|
||||
|
||||
export const WRoutesUser = () => {
|
||||
const {
|
||||
@@ -61,9 +62,17 @@ export const WRoutesUser = () => {
|
||||
[userHubs, outCommand],
|
||||
);
|
||||
|
||||
// INFO: User routes loading only if open widget with user routes
|
||||
const { loading, setLoading } = useLoadRoutes({
|
||||
data: settingsRoutes,
|
||||
hubs: userHubs,
|
||||
loadRoutesCommand,
|
||||
routesList: userRoutes,
|
||||
});
|
||||
|
||||
useMapEventListener(event => {
|
||||
if (event.name === Commands.userRoutes) {
|
||||
ref.current?.stopLoading();
|
||||
setLoading(false);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
@@ -76,7 +85,7 @@ export const WRoutesUser = () => {
|
||||
update={settingsRoutesUpdate}
|
||||
hubs={userHubs}
|
||||
routesList={userRoutes}
|
||||
loadRoutesCommand={loadRoutesCommand}
|
||||
loading={loading}
|
||||
addHubCommand={addHubCommand}
|
||||
toggleHubCommand={toggleHubCommand}
|
||||
isRestricted
|
||||
|
||||
@@ -13,6 +13,7 @@ import { useCharacterActivityHandlers } from './hooks/useCharacterActivityHandle
|
||||
import { TrackingDialog } from '@/hooks/Mapper/components/mapRootContent/components/TrackingDialog';
|
||||
import { useMapEventListener } from '@/hooks/Mapper/events';
|
||||
import { Commands } from '@/hooks/Mapper/types';
|
||||
import { PingsInterface } from '@/hooks/Mapper/components/mapInterface/components';
|
||||
|
||||
export interface MapRootContentProps {}
|
||||
|
||||
@@ -62,17 +63,21 @@ export const MapRootContent = ({}: MapRootContentProps) => {
|
||||
onShowOnTheMap={handleShowOnTheMap}
|
||||
onShowMapSettings={handleShowMapSettings}
|
||||
onShowTrackingDialog={handleShowTrackingDialog}
|
||||
additionalContent={<PingsInterface hasLeftOffset />}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="absolute top-0 left-14 w-[calc(100%-3.5rem)] h-[calc(100%-3.5rem)] pointer-events-none">
|
||||
<Topbar>
|
||||
<MapContextMenu
|
||||
onShowOnTheMap={handleShowOnTheMap}
|
||||
onShowMapSettings={handleShowMapSettings}
|
||||
onShowTrackingDialog={handleShowTrackingDialog}
|
||||
/>
|
||||
<div className="flex items-center ml-1">
|
||||
<PingsInterface />
|
||||
<MapContextMenu
|
||||
onShowOnTheMap={handleShowOnTheMap}
|
||||
onShowMapSettings={handleShowMapSettings}
|
||||
onShowTrackingDialog={handleShowTrackingDialog}
|
||||
/>
|
||||
</div>
|
||||
</Topbar>
|
||||
{mapInterface}
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,10 @@ export interface MapContextMenuProps {
|
||||
}
|
||||
|
||||
export const MapContextMenu = ({ onShowOnTheMap, onShowMapSettings, onShowTrackingDialog }: MapContextMenuProps) => {
|
||||
const { outCommand, setInterfaceSettings } = useMapRootState();
|
||||
const {
|
||||
outCommand,
|
||||
storedSettings: { setInterfaceSettings },
|
||||
} = useMapRootState();
|
||||
|
||||
const canTrackCharacters = useMapCheckPermissions([UserPermission.TRACK_CHARACTER]);
|
||||
|
||||
|
||||
@@ -4,13 +4,7 @@ import { useCallback, useRef, useState } from 'react';
|
||||
import { TabPanel, TabView } from 'primereact/tabview';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { OutCommand } from '@/hooks/Mapper/types';
|
||||
import {
|
||||
CONNECTIONS_CHECKBOXES_PROPS,
|
||||
SIGNATURES_CHECKBOXES_PROPS,
|
||||
SYSTEMS_CHECKBOXES_PROPS,
|
||||
THEME_SETTING,
|
||||
UI_CHECKBOXES_PROPS,
|
||||
} from './constants.ts';
|
||||
import { CONNECTIONS_CHECKBOXES_PROPS, SIGNATURES_CHECKBOXES_PROPS, SYSTEMS_CHECKBOXES_PROPS } from './constants.ts';
|
||||
import {
|
||||
MapSettingsProvider,
|
||||
useMapSettings,
|
||||
@@ -34,6 +28,8 @@ export const MapSettingsComp = ({ visible, onHide }: MapSettingsProps) => {
|
||||
refVars.current = { outCommand, onHide, visible };
|
||||
|
||||
const handleShow = useCallback(async () => {
|
||||
// TODO: need fix it - add type?
|
||||
// @ts-ignore
|
||||
const { user_settings } = await refVars.current.outCommand({
|
||||
type: OutCommand.getUserSettings,
|
||||
data: null,
|
||||
@@ -88,17 +84,9 @@ export const MapSettingsComp = ({ visible, onHide }: MapSettingsProps) => {
|
||||
{renderSettingsList(SIGNATURES_CHECKBOXES_PROPS)}
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel header="User Interface" headerClassName={styles.verticalTabHeader}>
|
||||
{renderSettingsList(UI_CHECKBOXES_PROPS)}
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel header="Widgets" className="h-full" headerClassName={styles.verticalTabHeader}>
|
||||
<WidgetsSettings />
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel header="Theme" headerClassName={styles.verticalTabHeader}>
|
||||
{renderSettingItem(THEME_SETTING)}
|
||||
</TabPanel>
|
||||
</TabView>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -88,8 +88,9 @@ export const MapSettingsProvider = ({ children }: { children: ReactNode }) => {
|
||||
|
||||
if (item.type === 'dropdown' && item.options) {
|
||||
return (
|
||||
<div key={item.prop.toString()} className="flex items-center gap-2 mt-2">
|
||||
<label className="text-sm">{item.label}:</label>
|
||||
<div key={item.prop.toString()} className="grid grid-cols-[auto_1fr_auto] items-center">
|
||||
<label className="text-[var(--gray-200)] text-[13px] select-none">{item.label}:</label>
|
||||
<div className="border-b-2 border-dotted border-[#3f3f3f] h-px mx-3" />
|
||||
<Dropdown
|
||||
className="text-sm"
|
||||
value={currentValue}
|
||||
|
||||
@@ -1,13 +1,34 @@
|
||||
import { COMMON_CHECKBOXES_PROPS } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts';
|
||||
import {
|
||||
MINI_MAP_PLACEMENT,
|
||||
PINGS_PLACEMENT,
|
||||
THEME_SETTING,
|
||||
UI_CHECKBOXES_PROPS,
|
||||
} from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/constants.ts';
|
||||
import { useMapSettings } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettingsProvider.tsx';
|
||||
import { SettingsListItem } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/types.ts';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export const CommonSettings = () => {
|
||||
const { renderSettingItem } = useMapSettings();
|
||||
|
||||
const renderSettingsList = (list: SettingsListItem[]) => {
|
||||
return list.map(renderSettingItem);
|
||||
};
|
||||
const renderSettingsList = useCallback(
|
||||
(list: SettingsListItem[]) => {
|
||||
return list.map(renderSettingItem);
|
||||
},
|
||||
[renderSettingItem],
|
||||
);
|
||||
|
||||
return <div className="w-full h-full flex flex-col gap-1">{renderSettingsList(COMMON_CHECKBOXES_PROPS)}</div>;
|
||||
return (
|
||||
<div className="flex flex-col h-full gap-1">
|
||||
<div>
|
||||
<div className="w-full h-full flex flex-col gap-1">{renderSettingsList(UI_CHECKBOXES_PROPS)}</div>
|
||||
</div>
|
||||
|
||||
<div className="border-b-2 border-dotted border-stone-700/50 h-px my-3" />
|
||||
|
||||
<div className="grid grid-cols-[1fr_auto]">{renderSettingItem(MINI_MAP_PLACEMENT)}</div>
|
||||
<div className="grid grid-cols-[1fr_auto]">{renderSettingItem(PINGS_PLACEMENT)}</div>
|
||||
<div className="grid grid-cols-[1fr_auto]">{renderSettingItem(THEME_SETTING)}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -10,9 +10,9 @@ interface PrettySwitchboxProps {
|
||||
|
||||
export const PrettySwitchbox = ({ checked, setChecked, label }: PrettySwitchboxProps) => {
|
||||
return (
|
||||
<label className={styles.CheckboxContainer}>
|
||||
<span>{label}</span>
|
||||
<div />
|
||||
<label className="grid grid-cols-[auto_1fr_auto] items-center">
|
||||
<span className="text-[var(--gray-200)] text-[13px] select-none">{label}</span>
|
||||
<div className="border-b-2 border-dotted border-[#3f3f3f] h-px mx-3" />
|
||||
<div className={styles.smallInputSwitch}>
|
||||
<WdCheckbox size="m" label={''} value={checked} onChange={e => setChecked(e.checked ?? false)} />
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { SettingsListItem, UserSettingsRemoteProps } from './types.ts';
|
||||
import { InterfaceStoredSettingsProps } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { AvailableThemes } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
import { AvailableThemes, MiniMapPlacement, PingsPlacement } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
|
||||
export const DEFAULT_REMOTE_SETTINGS = {
|
||||
[UserSettingsRemoteProps.link_signature_on_splash]: false,
|
||||
@@ -14,13 +14,13 @@ export const UserSettingsRemoteList = [
|
||||
UserSettingsRemoteProps.delete_connection_with_sigs,
|
||||
];
|
||||
|
||||
export const COMMON_CHECKBOXES_PROPS: SettingsListItem[] = [
|
||||
{
|
||||
prop: InterfaceStoredSettingsProps.isShowMinimap,
|
||||
label: 'Show Minimap',
|
||||
type: 'checkbox',
|
||||
},
|
||||
];
|
||||
// export const COMMON_CHECKBOXES_PROPS: SettingsListItem[] = [
|
||||
// // {
|
||||
// // prop: InterfaceStoredSettingsProps.isShowMinimap,
|
||||
// // label: 'Show Minimap',
|
||||
// // type: 'checkbox',
|
||||
// // },
|
||||
// ];
|
||||
|
||||
export const SYSTEMS_CHECKBOXES_PROPS: SettingsListItem[] = [
|
||||
{
|
||||
@@ -90,3 +90,32 @@ export const THEME_SETTING: SettingsListItem = {
|
||||
type: 'dropdown',
|
||||
options: THEME_OPTIONS,
|
||||
};
|
||||
|
||||
export const MINI_MAP_PLACEMENT_OPTIONS = [
|
||||
{ label: 'Right Bottom', value: MiniMapPlacement.rightBottom },
|
||||
{ label: 'Right Top', value: MiniMapPlacement.rightTop },
|
||||
{ label: 'Left Top', value: MiniMapPlacement.leftTop },
|
||||
{ label: 'Left Bottom', value: MiniMapPlacement.leftBottom },
|
||||
{ label: 'Hide', value: MiniMapPlacement.hide },
|
||||
];
|
||||
|
||||
export const MINI_MAP_PLACEMENT: SettingsListItem = {
|
||||
prop: 'minimapPlacement',
|
||||
label: 'Minimap Placement',
|
||||
type: 'dropdown',
|
||||
options: MINI_MAP_PLACEMENT_OPTIONS,
|
||||
};
|
||||
|
||||
export const PINGS_PLACEMENT_OPTIONS = [
|
||||
{ label: 'Right Top', value: PingsPlacement.rightTop },
|
||||
{ label: 'Left Top', value: PingsPlacement.leftTop },
|
||||
{ label: 'Left Bottom', value: PingsPlacement.leftBottom },
|
||||
{ label: 'Right Bottom', value: PingsPlacement.rightBottom },
|
||||
];
|
||||
|
||||
export const PINGS_PLACEMENT: SettingsListItem = {
|
||||
prop: 'pingsPlacement',
|
||||
label: 'Pings Placement',
|
||||
type: 'dropdown',
|
||||
options: PINGS_PLACEMENT_OPTIONS,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { InterfaceStoredSettings } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { InterfaceStoredSettings } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
|
||||
export enum UserSettingsRemoteProps {
|
||||
link_signature_on_splash = 'link_signature_on_splash',
|
||||
|
||||
@@ -35,7 +35,7 @@ const itemTemplate = (item: CharacterTypeRaw & WithIsOwnCharacter, options: Virt
|
||||
})}
|
||||
style={{ height: options.props.itemSize + 'px' }}
|
||||
>
|
||||
<CharacterCard showCorporationLogo showAllyLogo showSystem showTicker {...item} />
|
||||
<CharacterCard showCorporationLogo showAllyLogo showSystem showTicker showShip {...item} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import classes from './RightBar.module.scss';
|
||||
import clsx from 'clsx';
|
||||
import { useCallback } from 'react';
|
||||
import { ReactNode, useCallback } from 'react';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||
import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit';
|
||||
@@ -12,24 +12,21 @@ interface RightBarProps {
|
||||
onShowOnTheMap?: () => void;
|
||||
onShowMapSettings?: () => void;
|
||||
onShowTrackingDialog?: () => void;
|
||||
additionalContent?: ReactNode;
|
||||
}
|
||||
|
||||
export const RightBar = ({ onShowOnTheMap, onShowMapSettings, onShowTrackingDialog }: RightBarProps) => {
|
||||
export const RightBar = ({
|
||||
onShowOnTheMap,
|
||||
onShowMapSettings,
|
||||
onShowTrackingDialog,
|
||||
additionalContent,
|
||||
}: RightBarProps) => {
|
||||
const {
|
||||
storedSettings: { interfaceSettings, setInterfaceSettings },
|
||||
} = useMapRootState();
|
||||
|
||||
const canTrackCharacters = useMapCheckPermissions([UserPermission.TRACK_CHARACTER]);
|
||||
|
||||
const isShowMinimap = interfaceSettings.isShowMinimap === undefined ? true : interfaceSettings.isShowMinimap;
|
||||
|
||||
const toggleMinimap = useCallback(() => {
|
||||
setInterfaceSettings(x => ({
|
||||
...x,
|
||||
isShowMinimap: !x.isShowMinimap,
|
||||
}));
|
||||
}, [setInterfaceSettings]);
|
||||
|
||||
const toggleKSpace = useCallback(() => {
|
||||
setInterfaceSettings(x => ({
|
||||
...x,
|
||||
@@ -78,6 +75,7 @@ export const RightBar = ({ onShowOnTheMap, onShowMapSettings, onShowTrackingDial
|
||||
</WdTooltipWrapper>
|
||||
</>
|
||||
)}
|
||||
{additionalContent}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center mb-2 gap-1">
|
||||
@@ -106,16 +104,6 @@ export const RightBar = ({ onShowOnTheMap, onShowMapSettings, onShowTrackingDial
|
||||
</button>
|
||||
</WdTooltipWrapper>
|
||||
|
||||
<WdTooltipWrapper content={isShowMinimap ? 'Hide minimap' : 'Show minimap'} position={TooltipPosition.left}>
|
||||
<button
|
||||
className="btn bg-transparent text-gray-400 hover:text-white border-transparent hover:bg-transparent py-2 h-auto min-h-auto"
|
||||
type="button"
|
||||
onClick={toggleMinimap}
|
||||
>
|
||||
<i className={isShowMinimap ? 'pi pi-eye' : 'pi pi-eye-slash'}></i>
|
||||
</button>
|
||||
</WdTooltipWrapper>
|
||||
|
||||
<WdTooltipWrapper content="Switch to menu" position={TooltipPosition.left}>
|
||||
<button
|
||||
className="btn bg-transparent text-gray-400 hover:text-white border-transparent hover:bg-transparent py-2 h-auto min-h-auto"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Map, MAP_ROOT_ID } from '@/hooks/Mapper/components/map/Map.tsx';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { OutCommand, OutCommandHandler, SolarSystemConnection } from '@/hooks/Mapper/types';
|
||||
import { MapRootData, useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { OnMapAddSystemCallback, OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts';
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
SystemLinkSignatureDialog,
|
||||
SystemSettingsDialog,
|
||||
} from '@/hooks/Mapper/components/mapInterface/components';
|
||||
import classes from './MapWrapper.module.scss';
|
||||
import { Connections } from '@/hooks/Mapper/components/mapRootContent/components/Connections';
|
||||
import { ContextMenuSystemMultiple, useContextMenuSystemMultipleHandlers } from '../contexts/ContextMenuSystemMultiple';
|
||||
import { getSystemById } from '@/hooks/Mapper/helpers';
|
||||
@@ -27,26 +26,40 @@ 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';
|
||||
import { PingType } from '@/hooks/Mapper/types/ping.ts';
|
||||
import { SystemPingDialog } from '@/hooks/Mapper/components/mapInterface/components/SystemPingDialog';
|
||||
import { MiniMapPlacement } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
import { MINIMAP_PLACEMENT_MAP } from '@/hooks/Mapper/constants.ts';
|
||||
import type { PanelPosition } from '@reactflow/core';
|
||||
import { MINI_MAP_PLACEMENT_OFFSETS } from './constants.ts';
|
||||
|
||||
// TODO: INFO - this component needs for abstract work with Map instance
|
||||
export const MapWrapper = () => {
|
||||
const {
|
||||
update,
|
||||
outCommand,
|
||||
data: { selectedConnections, selectedSystems, hubs, userHubs, systems, linkSignatureToSystem, systemSignatures },
|
||||
data: {
|
||||
pings,
|
||||
selectedConnections,
|
||||
selectedSystems,
|
||||
hubs,
|
||||
userHubs,
|
||||
systems,
|
||||
linkSignatureToSystem,
|
||||
systemSignatures,
|
||||
},
|
||||
storedSettings: { interfaceSettings },
|
||||
} = useMapRootState();
|
||||
|
||||
const {
|
||||
isShowMenu,
|
||||
isShowMinimap = STORED_INTERFACE_DEFAULT_VALUES.isShowMinimap,
|
||||
isShowKSpace,
|
||||
isThickConnections,
|
||||
isShowBackgroundPattern,
|
||||
isShowUnsplashedSignatures,
|
||||
isSoftBackground,
|
||||
theme,
|
||||
minimapPlacement,
|
||||
} = interfaceSettings;
|
||||
|
||||
const { deleteSystems } = useDeleteSystems();
|
||||
@@ -58,6 +71,7 @@ export const MapWrapper = () => {
|
||||
const { handleSystemMultipleContext, ...systemMultipleCtxProps } = useContextMenuSystemMultipleHandlers();
|
||||
|
||||
const [openSettings, setOpenSettings] = useState<string | null>(null);
|
||||
const [openPing, setOpenPing] = useState<{ type: PingType; solar_system_id: string } | null>(null);
|
||||
const [openCustomLabel, setOpenCustomLabel] = useState<string | null>(null);
|
||||
const [openAddSystem, setOpenAddSystem] = useState<XYPosition | null>(null);
|
||||
const [selectedConnection, setSelectedConnection] = useState<SolarSystemConnection | null>(null);
|
||||
@@ -99,6 +113,8 @@ export const MapWrapper = () => {
|
||||
event => {
|
||||
switch (event.type) {
|
||||
case OutCommand.openSettings:
|
||||
// TODO - need fix it
|
||||
// @ts-ignore
|
||||
setOpenSettings(event.data.system_id);
|
||||
break;
|
||||
default:
|
||||
@@ -111,6 +127,7 @@ export const MapWrapper = () => {
|
||||
);
|
||||
|
||||
const handleSystemContextMenu = useCallback(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(ev: any, systemId: string) => {
|
||||
const { selectedSystems, systems } = ref.current;
|
||||
if (selectedSystems.length > 1) {
|
||||
@@ -130,11 +147,13 @@ export const MapWrapper = () => {
|
||||
const handleDeleteSelected = useCallback(() => {
|
||||
const restDel = getNodes()
|
||||
.filter(x => x.selected && !x.data.locked)
|
||||
.filter(x => !pings.some(p => x.data.id === p.solar_system_id))
|
||||
.map(x => x.data.id);
|
||||
|
||||
if (restDel.length > 0) {
|
||||
ref.current.deleteSystems(restDel);
|
||||
}
|
||||
}, [getNodes]);
|
||||
}, [getNodes, pings]);
|
||||
|
||||
const onAddSystem: OnMapAddSystemCallback = useCallback(({ coordinates }) => {
|
||||
setOpenAddSystem(coordinates);
|
||||
@@ -158,6 +177,27 @@ export const MapWrapper = () => {
|
||||
[openAddSystem, outCommand],
|
||||
);
|
||||
|
||||
const handleOpenSettings = useCallback(() => {
|
||||
ref.current.systemContextProps.systemId && setOpenSettings(ref.current.systemContextProps.systemId);
|
||||
}, []);
|
||||
|
||||
const handleTogglePing = useCallback(async (type: PingType, solar_system_id: string, hasPing: boolean) => {
|
||||
if (hasPing) {
|
||||
await outCommand({
|
||||
type: OutCommand.cancelPing,
|
||||
data: { type, solar_system_id: solar_system_id },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setOpenPing({ type, solar_system_id });
|
||||
}, []);
|
||||
|
||||
const handleCustomLabelDialog = useCallback(() => {
|
||||
const { systemContextProps } = ref.current;
|
||||
systemContextProps.systemId && setOpenCustomLabel(systemContextProps.systemId);
|
||||
}, []);
|
||||
|
||||
useHotkey(false, ['Delete'], (event: KeyboardEvent) => {
|
||||
const targetWindow = (event.target as HTMLHtmlElement)?.closest(`[data-window-id="${MAP_ROOT_ID}"]`);
|
||||
|
||||
@@ -179,6 +219,22 @@ export const MapWrapper = () => {
|
||||
outCommand({ type: OutCommand.loadSignatures, data: {} });
|
||||
}, [isShowUnsplashedSignatures, systems]);
|
||||
|
||||
const { showMinimap, minimapPosition, minimapClasses } = useMemo(() => {
|
||||
const rawPlacement = minimapPlacement == null ? MiniMapPlacement.rightBottom : minimapPlacement;
|
||||
|
||||
if (rawPlacement === MiniMapPlacement.hide) {
|
||||
return { minimapPosition: undefined, showMinimap: false, minimapClasses: '' };
|
||||
}
|
||||
|
||||
const mmClasses = MINI_MAP_PLACEMENT_OFFSETS[rawPlacement];
|
||||
|
||||
return {
|
||||
minimapPosition: MINIMAP_PLACEMENT_MAP[rawPlacement] as PanelPosition,
|
||||
showMinimap: true,
|
||||
minimapClasses: isShowMenu ? mmClasses.default : mmClasses.withLeftMenu,
|
||||
};
|
||||
}, [minimapPlacement, isShowMenu]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Map
|
||||
@@ -188,19 +244,29 @@ export const MapWrapper = () => {
|
||||
onConnectionInfoClick={handleConnectionDbClick}
|
||||
onSystemContextMenu={handleSystemContextMenu}
|
||||
onSelectionContextMenu={handleSystemMultipleContext}
|
||||
minimapClasses={!isShowMenu ? classes.MiniMap : undefined}
|
||||
isShowMinimap={isShowMinimap}
|
||||
minimapClasses={minimapClasses}
|
||||
isShowMinimap={showMinimap}
|
||||
showKSpaceBG={isShowKSpace}
|
||||
isThickConnections={isThickConnections}
|
||||
isShowBackgroundPattern={isShowBackgroundPattern}
|
||||
isSoftBackground={isSoftBackground}
|
||||
theme={theme}
|
||||
pings={pings}
|
||||
onAddSystem={onAddSystem}
|
||||
minimapPlacement={minimapPosition}
|
||||
/>
|
||||
|
||||
{openSettings != null && (
|
||||
<SystemSettingsDialog systemId={openSettings} visible setVisible={() => setOpenSettings(null)} />
|
||||
)}
|
||||
{openPing != null && (
|
||||
<SystemPingDialog
|
||||
systemId={openPing.solar_system_id}
|
||||
type={openPing.type}
|
||||
visible
|
||||
setVisible={() => setOpenPing(null)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{openCustomLabel != null && (
|
||||
<SystemCustomLabelDialog systemId={openCustomLabel} visible setVisible={() => setOpenCustomLabel(null)} />
|
||||
@@ -223,13 +289,9 @@ export const MapWrapper = () => {
|
||||
hubs={hubs}
|
||||
userHubs={userHubs}
|
||||
{...systemContextProps}
|
||||
onOpenSettings={() => {
|
||||
systemContextProps.systemId && setOpenSettings(systemContextProps.systemId);
|
||||
}}
|
||||
onCustomLabelDialog={() => {
|
||||
const { systemContextProps } = ref.current;
|
||||
systemContextProps.systemId && setOpenCustomLabel(systemContextProps.systemId);
|
||||
}}
|
||||
onOpenSettings={handleOpenSettings}
|
||||
onTogglePing={handleTogglePing}
|
||||
onCustomLabelDialog={handleCustomLabelDialog}
|
||||
/>
|
||||
|
||||
<ContextMenuSystemMultiple {...systemMultipleCtxProps} />
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { MiniMapPlacement } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
|
||||
export const MINI_MAP_PLACEMENT_OFFSETS = {
|
||||
[MiniMapPlacement.rightTop]: { default: '!top-[48px]', withLeftMenu: '!top-[48px] !right-[58px]' },
|
||||
[MiniMapPlacement.rightBottom]: { default: '!bottom-[0px]', withLeftMenu: '!bottom-[0px] !right-[58px]' },
|
||||
[MiniMapPlacement.leftTop]: { default: '!top-[48px] !left-[56px]', withLeftMenu: '!top-[48px] !left-[56px]' },
|
||||
[MiniMapPlacement.leftBottom]: { default: '!left-[56px] !bottom-[0px]', withLeftMenu: '!left-[56px] !bottom-[0px]' },
|
||||
};
|
||||
@@ -4,10 +4,11 @@ import { useMemo } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { sortOnlineFunc } from '@/hooks/Mapper/components/hooks/useGetOwnOnlineCharacters.ts';
|
||||
import { WithChildren } from '@/hooks/Mapper/types/common.ts';
|
||||
import { Button } from 'primereact/button';
|
||||
|
||||
const Topbar = ({ children }: WithChildren) => {
|
||||
const {
|
||||
data: { characters, userCharacters },
|
||||
data: { characters, userCharacters, pings },
|
||||
} = useMapRootState();
|
||||
|
||||
const charsToShow = useMemo(() => {
|
||||
@@ -24,7 +25,10 @@ const Topbar = ({ children }: WithChildren) => {
|
||||
>
|
||||
<span className="flex-1"></span>
|
||||
<span className="mr-2"></span>
|
||||
<Characters data={charsToShow} />
|
||||
<div className="flex gap-1 items-center">
|
||||
<Characters data={charsToShow} />
|
||||
</div>
|
||||
|
||||
{children}
|
||||
</nav>
|
||||
);
|
||||
|
||||
@@ -13,17 +13,22 @@ import {
|
||||
} from '@/hooks/Mapper/components/ui-kit';
|
||||
import { isDocked } from '@/hooks/Mapper/helpers/isDocked.ts';
|
||||
import classes from './CharacterCard.module.scss';
|
||||
import { WithClassName } from '@/hooks/Mapper/types/common.ts';
|
||||
|
||||
type CharacterCardProps = {
|
||||
export type CharacterCardProps = {
|
||||
compact?: boolean;
|
||||
showSystem?: boolean;
|
||||
showTicker?: boolean;
|
||||
showShip?: boolean;
|
||||
showShipName?: boolean;
|
||||
useSystemsCache?: boolean;
|
||||
showCorporationLogo?: boolean;
|
||||
showAllyLogo?: boolean;
|
||||
} & CharacterTypeRaw &
|
||||
WithIsOwnCharacter;
|
||||
simpleMode?: boolean;
|
||||
} & WithIsOwnCharacter &
|
||||
WithClassName;
|
||||
|
||||
type CharacterCardInnerProps = CharacterCardProps & CharacterTypeRaw;
|
||||
|
||||
const SHIP_NAME_RX = /u'|'/g;
|
||||
export const getShipName = (name: string) => {
|
||||
@@ -34,16 +39,19 @@ export const getShipName = (name: string) => {
|
||||
};
|
||||
|
||||
export const CharacterCard = ({
|
||||
simpleMode,
|
||||
compact = false,
|
||||
isOwn,
|
||||
showSystem,
|
||||
showShip,
|
||||
showShipName,
|
||||
showCorporationLogo,
|
||||
showAllyLogo,
|
||||
showTicker,
|
||||
useSystemsCache,
|
||||
className,
|
||||
...char
|
||||
}: CharacterCardProps) => {
|
||||
}: CharacterCardInnerProps) => {
|
||||
const handleSelect = useCallback(() => {
|
||||
emitMapEvent({
|
||||
name: Commands.centerSystem,
|
||||
@@ -56,9 +64,55 @@ export const CharacterCard = ({
|
||||
const shipType = char.ship?.ship_type_info?.name;
|
||||
const locationShown = showSystem && char.location?.solar_system_id;
|
||||
|
||||
// INFO: Simple mode show only name and icon of ally/corp. By default it compact view
|
||||
if (simpleMode) {
|
||||
return (
|
||||
<div className={clsx('text-xs box-border', className)} onClick={handleSelect}>
|
||||
<div className="flex items-center gap-1 relative">
|
||||
<WdEveEntityPortrait eveId={char.eve_id} size={WdEveEntityPortraitSize.w18} />
|
||||
|
||||
{!char.alliance_id && (
|
||||
<WdTooltipWrapper position={TooltipPosition.top} content={char.corporation_name}>
|
||||
<WdEveEntityPortrait
|
||||
type={WdEveEntityPortraitType.corporation}
|
||||
eveId={char.corporation_id.toString()}
|
||||
size={WdEveEntityPortraitSize.w18}
|
||||
/>
|
||||
</WdTooltipWrapper>
|
||||
)}
|
||||
|
||||
{char.alliance_id && (
|
||||
<WdTooltipWrapper position={TooltipPosition.top} content={char.alliance_name}>
|
||||
<WdEveEntityPortrait
|
||||
type={WdEveEntityPortraitType.alliance}
|
||||
eveId={char.alliance_id.toString()}
|
||||
size={WdEveEntityPortraitSize.w18}
|
||||
/>
|
||||
</WdTooltipWrapper>
|
||||
)}
|
||||
|
||||
<div className="flex overflow-hidden text-left">
|
||||
<div className="flex">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (compact) {
|
||||
return (
|
||||
<div className="text-xs box-border w-full" onClick={handleSelect}>
|
||||
<div className={clsx('text-xs box-border w-full', className)} onClick={handleSelect}>
|
||||
<div className="w-full flex items-center gap-1 relative">
|
||||
<WdEveEntityPortrait eveId={char.eve_id} size={WdEveEntityPortraitSize.w18} />
|
||||
|
||||
@@ -98,7 +152,7 @@ export const CharacterCard = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{shipType && (
|
||||
{showShip && shipType && (
|
||||
<>
|
||||
{!showShipName && (
|
||||
<div
|
||||
@@ -192,7 +246,7 @@ export const CharacterCard = ({
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
{shipType && (
|
||||
{showShip && shipType && (
|
||||
<>
|
||||
<div className="flex flex-col flex-shrink-0 items-end self-start">
|
||||
<div
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { CharacterCard, CharacterCardProps } from '@/hooks/Mapper/components/ui-kit/CharacterCard';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
type CharacterCardByIdProps = {
|
||||
characterId: string;
|
||||
} & Omit<CharacterCardProps, 'isOwn'>;
|
||||
|
||||
export const CharacterCardById = ({ characterId, ...props }: CharacterCardByIdProps) => {
|
||||
const {
|
||||
data: { characters },
|
||||
} = useMapRootState();
|
||||
|
||||
const charInfo = useMemo(() => {
|
||||
return characters.find(x => x.eve_id === characterId);
|
||||
}, [characterId, characters]);
|
||||
|
||||
if (!charInfo) {
|
||||
return 'No character found.';
|
||||
}
|
||||
|
||||
return <CharacterCard isOwn={false} {...charInfo} {...props} />;
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './CharacterCardById';
|
||||
@@ -0,0 +1,11 @@
|
||||
import Markdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import remarkBreaks from 'remark-breaks';
|
||||
|
||||
const REMARK_PLUGINS = [remarkGfm, remarkBreaks];
|
||||
|
||||
type MarkdownTextViewerProps = { children: string };
|
||||
|
||||
export const MarkdownTextViewer = ({ children }: MarkdownTextViewerProps) => {
|
||||
return <Markdown remarkPlugins={REMARK_PLUGINS}>{children}</Markdown>;
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { WithChildren } from '@/hooks/Mapper/types/common.ts';
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||
import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit/WdTooltip';
|
||||
import clsx from 'clsx';
|
||||
|
||||
type MenuItemWithInfoProps = { infoTitle: ReactNode; infoClass?: string } & WithChildren;
|
||||
export const MenuItemWithInfo = ({ children, infoClass, infoTitle }: MenuItemWithInfoProps) => {
|
||||
return (
|
||||
<div className="flex justify-between w-full h-full items-center">
|
||||
{children}
|
||||
<WdTooltipWrapper
|
||||
content={infoTitle}
|
||||
position={TooltipPosition.top}
|
||||
className="!opacity-100 !pointer-events-auto"
|
||||
>
|
||||
<div className={clsx('pi text-orange-400', infoClass)} />
|
||||
</WdTooltipWrapper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +1,4 @@
|
||||
import { WithClassName } from '@/hooks/Mapper/types/common.ts';
|
||||
import { SystemViewStandalone } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { SystemViewStandalone, SystemViewStandaloneProps } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts';
|
||||
import { useMemo } from 'react';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
@@ -8,18 +7,11 @@ import { SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types';
|
||||
export type SystemViewProps = {
|
||||
systemId: string;
|
||||
systemInfo?: SolarSystemStaticInfoRaw;
|
||||
hideRegion?: boolean;
|
||||
useSystemsCache?: boolean;
|
||||
showCustomName?: boolean;
|
||||
} & WithClassName;
|
||||
} & Pick<SystemViewStandaloneProps, 'className' | 'compact' | 'hideRegion'>;
|
||||
|
||||
export const SystemView = ({
|
||||
systemId,
|
||||
systemInfo: customSystemInfo,
|
||||
hideRegion,
|
||||
className,
|
||||
showCustomName,
|
||||
}: SystemViewProps) => {
|
||||
export const SystemView = ({ systemId, systemInfo: customSystemInfo, showCustomName, ...rest }: SystemViewProps) => {
|
||||
const memSystems = useMemo(() => [systemId], [systemId]);
|
||||
const { systems, loading } = useLoadSystemStatic({ systems: memSystems });
|
||||
|
||||
@@ -47,13 +39,8 @@ export const SystemView = ({
|
||||
}
|
||||
|
||||
if (!mapSystemInfo) {
|
||||
return <SystemViewStandalone hideRegion={hideRegion} className={className} {...systemInfo} />;
|
||||
return <SystemViewStandalone {...rest} {...systemInfo} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SystemViewStandalone hideRegion={hideRegion} className={className} {...systemInfo} />
|
||||
<span>{systemInfo.solar_system_name}</span>
|
||||
</div>
|
||||
);
|
||||
return <SystemViewStandalone customName={mapSystemInfo.name ?? undefined} {...rest} {...systemInfo} />;
|
||||
};
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useEffect, useState, useRef } from 'react';
|
||||
import { WithClassName } from '@/hooks/Mapper/types/common.ts';
|
||||
|
||||
interface TimeAgoProps {
|
||||
timestamp: string; // Теперь тип string, так как приходит ISO 8601 строка
|
||||
}
|
||||
|
||||
export const TimeAgo = ({ timestamp }: TimeAgoProps) => {
|
||||
export const TimeAgo = ({ timestamp, className }: TimeAgoProps & WithClassName) => {
|
||||
const [timeAgo, setTimeAgo] = useState<string>('');
|
||||
const timeoutIdRef = useRef<number | null>(null);
|
||||
|
||||
@@ -64,5 +65,5 @@ export const TimeAgo = ({ timestamp }: TimeAgoProps) => {
|
||||
return `${days} days ago`;
|
||||
};
|
||||
|
||||
return <span>{timeAgo}</span>;
|
||||
return <span className={className}>{timeAgo}</span>;
|
||||
};
|
||||
|
||||
@@ -11,12 +11,14 @@ export enum WdImageSize {
|
||||
large = 'large',
|
||||
}
|
||||
|
||||
export type WdImgButtonTooltip = Pick<WdTooltipWrapperProps, 'content' | 'position' | 'offset' | 'className'>;
|
||||
|
||||
export type WdImgButtonProps = {
|
||||
onClick?(e: MouseEvent): void;
|
||||
source?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
tooltip?: Pick<WdTooltipWrapperProps, 'content' | 'position' | 'offset' | 'className'>;
|
||||
tooltip?: WdImgButtonTooltip;
|
||||
textSize?: WdImageSize;
|
||||
} & WithClassName &
|
||||
HTMLProps<HTMLDivElement>;
|
||||
|
||||
16
assets/js/hooks/Mapper/components/ui-kit/WdMenuItem.tsx
Normal file
16
assets/js/hooks/Mapper/components/ui-kit/WdMenuItem.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { WithChildren } from '@/hooks/Mapper/types/common.ts';
|
||||
import clsx from 'clsx';
|
||||
|
||||
type WdMenuItemProps = { icon?: string; disabled?: boolean } & WithChildren;
|
||||
export const WdMenuItem = ({ children, icon, disabled }: WdMenuItemProps) => {
|
||||
return (
|
||||
<a
|
||||
className={clsx('flex gap-[6px] w-full h-full items-center px-[12px] !py-0 ml-[-2px]', 'p-menuitem-link', {
|
||||
'p-disabled': disabled,
|
||||
})}
|
||||
>
|
||||
{icon && <div className={clsx('min-w-[20px]', icon)}></div>}
|
||||
<div className="w-full">{children}</div>
|
||||
</a>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './CharacterCard';
|
||||
export * from './CharacterCardById';
|
||||
export * from './InfoDrawer';
|
||||
export * from './FixedTooltip';
|
||||
export * from './LayoutEventBlocker';
|
||||
@@ -17,3 +18,6 @@ export * from './WdRadioButton';
|
||||
export * from './WdEveEntityPortrait';
|
||||
export * from './WdTransition';
|
||||
export * from './LoadingWrapper';
|
||||
export * from './WdMenuItem';
|
||||
export * from './MenuItemWithInfo';
|
||||
export * from './MarkdownTextViewer.tsx';
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { PingsPlacement } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
|
||||
export enum SESSION_KEY {
|
||||
viewPort = 'viewPort',
|
||||
windows = 'windows',
|
||||
@@ -139,3 +141,10 @@ export const K162_TYPES_MAP: { [key: string]: K162Type } = K162_TYPES.reduce(
|
||||
(acc, x) => ({ ...acc, [x.value]: x }),
|
||||
{},
|
||||
);
|
||||
|
||||
export const MINIMAP_PLACEMENT_MAP = {
|
||||
[PingsPlacement.rightTop]: 'top-right',
|
||||
[PingsPlacement.leftTop]: 'top-left',
|
||||
[PingsPlacement.rightBottom]: 'bottom-right',
|
||||
[PingsPlacement.leftBottom]: 'bottom-left',
|
||||
};
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import {
|
||||
MAPPING_GROUP_TO_ENG,
|
||||
MAPPING_TYPE_TO_ENG,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||
import { SignatureGroup, SignatureKind, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { MAPPING_TYPE_TO_ENG } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||
|
||||
export const UNKNOWN_SIGNATURE_NAME = 'Unknown';
|
||||
|
||||
export const parseSignatures = (value: string, availableKeys: string[]): SystemSignature[] => {
|
||||
const outArr: SystemSignature[] = [];
|
||||
@@ -14,13 +19,39 @@ export const parseSignatures = (value: string, availableKeys: string[]): SystemS
|
||||
continue;
|
||||
}
|
||||
|
||||
const kind = MAPPING_TYPE_TO_ENG[sigArrInfo[1] as SignatureKind];
|
||||
// Extract the signature ID and check if it's valid (XXX-XXX format)
|
||||
const sigId = sigArrInfo[0];
|
||||
|
||||
if (!sigId || !sigId.match(/^[A-Z]{3}-\d{3}$/)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to map the kind, or fall back to CosmicSignature if unknown
|
||||
const typeString = sigArrInfo[1];
|
||||
let kind = SignatureKind.CosmicSignature;
|
||||
|
||||
// Try to map the kind using the flattened mapping
|
||||
const mappedKind = MAPPING_TYPE_TO_ENG[typeString];
|
||||
|
||||
if (mappedKind && availableKeys.includes(mappedKind)) {
|
||||
kind = mappedKind;
|
||||
}
|
||||
|
||||
// Try to map the group, or fall back to CosmicSignature if unknown
|
||||
const rawGroup = sigArrInfo[2];
|
||||
let group = SignatureGroup.CosmicSignature;
|
||||
|
||||
// Try to map the group using the flattened mapping
|
||||
const mappedGroup = MAPPING_GROUP_TO_ENG[rawGroup];
|
||||
if (mappedGroup) {
|
||||
group = mappedGroup;
|
||||
}
|
||||
|
||||
const signature: SystemSignature = {
|
||||
eve_id: sigArrInfo[0],
|
||||
kind: availableKeys.includes(kind) ? kind : SignatureKind.CosmicSignature,
|
||||
group: sigArrInfo[2] as SignatureGroup,
|
||||
name: sigArrInfo[3],
|
||||
eve_id: sigId,
|
||||
kind,
|
||||
group,
|
||||
name: sigArrInfo[3] || UNKNOWN_SIGNATURE_NAME,
|
||||
type: '',
|
||||
};
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ 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';
|
||||
import { useGlobalHooks } from '@/hooks/Mapper/mapRootProvider/hooks/useGlobalHooks.ts';
|
||||
|
||||
export type MapRootData = MapUnionTypes & {
|
||||
selectedSystems: string[];
|
||||
@@ -34,6 +35,7 @@ export type MapRootData = MapUnionTypes & {
|
||||
loading?: boolean;
|
||||
};
|
||||
trackingCharactersData: TrackingCharacter[];
|
||||
loadingPublicRoutes: boolean;
|
||||
};
|
||||
|
||||
const INITIAL_DATA: MapRootData = {
|
||||
@@ -66,11 +68,12 @@ const INITIAL_DATA: MapRootData = {
|
||||
linkSignatureToSystem: null,
|
||||
mainCharacterEveId: null,
|
||||
followingCharacterEveId: null,
|
||||
pings: [],
|
||||
loadingPublicRoutes: false,
|
||||
};
|
||||
|
||||
export enum InterfaceStoredSettingsProps {
|
||||
isShowMenu = 'isShowMenu',
|
||||
isShowMinimap = 'isShowMinimap',
|
||||
isShowKSpace = 'isShowKSpace',
|
||||
isThickConnections = 'isThickConnections',
|
||||
isShowUnsplashedSignatures = 'isShowUnsplashedSignatures',
|
||||
@@ -143,6 +146,7 @@ type MapRootProviderProps = {
|
||||
// eslint-disable-next-line react/display-name
|
||||
const MapRootHandlers = forwardRef(({ children }: WithChildren, fwdRef: ForwardedRef<any>) => {
|
||||
useMapRootHandlers(fwdRef);
|
||||
useGlobalHooks();
|
||||
return <>{children}</>;
|
||||
});
|
||||
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
import { AvailableThemes, InterfaceStoredSettings, RoutesType } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||
import {
|
||||
AvailableThemes,
|
||||
InterfaceStoredSettings,
|
||||
MiniMapPlacement,
|
||||
PingsPlacement,
|
||||
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,
|
||||
pingsPlacement: PingsPlacement.rightTop,
|
||||
minimapPlacement: MiniMapPlacement.rightBottom,
|
||||
};
|
||||
|
||||
export const DEFAULT_ROUTES_SETTINGS: RoutesType = {
|
||||
|
||||
@@ -9,3 +9,4 @@ export * from './useCommandsCharacters';
|
||||
export * from './useCommandComments';
|
||||
export * from './useGetCacheCharacter';
|
||||
export * from './useCommandsActivity';
|
||||
export * from './useCommandPings';
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { CommandPingAdded, CommandPingCancelled } from '@/hooks/Mapper/types';
|
||||
import { useCallback, useRef } from 'react';
|
||||
|
||||
export const useCommandPings = () => {
|
||||
const {
|
||||
update,
|
||||
data: { pings },
|
||||
} = useMapRootState();
|
||||
const ref = useRef({ update, pings });
|
||||
ref.current = { update, pings };
|
||||
|
||||
const pingAdded = useCallback((pings: CommandPingAdded) => {
|
||||
ref.current.update({ pings });
|
||||
}, []);
|
||||
|
||||
const pingCancelled = useCallback(({ type, solar_system_id }: CommandPingCancelled) => {
|
||||
const newPings = ref.current.pings.filter(x => x.solar_system_id !== solar_system_id && x.type !== type);
|
||||
ref.current.update({ pings: newPings });
|
||||
}, []);
|
||||
|
||||
return { pingAdded, pingCancelled };
|
||||
};
|
||||
@@ -70,6 +70,9 @@ export const useCommandsSystems = () => {
|
||||
const updateSystemSignatures = useCallback(
|
||||
async (systemId: string) => {
|
||||
const { update, systemSignatures } = ref.current;
|
||||
|
||||
// TODO need to fix it
|
||||
// @ts-ignore
|
||||
const { signatures } = await outCommand({
|
||||
type: OutCommand.getSignatures,
|
||||
data: { system_id: `${systemId}` },
|
||||
@@ -80,7 +83,7 @@ export const useCommandsSystems = () => {
|
||||
[outCommand],
|
||||
);
|
||||
|
||||
const updateLinkSignatureToSystem = useCallback(async (command: CommandLinkSignatureToSystem) => {
|
||||
const updateLinkSignatureToSystem = useCallback(async (command: CommandLinkSignatureToSystem | null) => {
|
||||
const { update } = ref.current;
|
||||
update({ linkSignatureToSystem: command }, true);
|
||||
}, []);
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { useLoadPublicRoutes } from './useLoadPublicRoutes';
|
||||
|
||||
/* TODO this hook needs for call some actions which should affect all mapper*/
|
||||
export const useGlobalHooks = () => {
|
||||
useLoadPublicRoutes();
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { LoadRoutesCommand } from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/types.ts';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { Commands, OutCommand } from '@/hooks/Mapper/types';
|
||||
import { useLoadRoutes } from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/hooks';
|
||||
import { useMapEventListener } from '@/hooks/Mapper/events';
|
||||
|
||||
export const useLoadPublicRoutes = () => {
|
||||
const {
|
||||
outCommand,
|
||||
storedSettings: { settingsRoutes },
|
||||
data: { hubs, routes, pings },
|
||||
update,
|
||||
} = useMapRootState();
|
||||
|
||||
const loadRoutesCommand: LoadRoutesCommand = useCallback(
|
||||
async (systemId, routesSettings) => {
|
||||
outCommand({
|
||||
type: OutCommand.getRoutes,
|
||||
data: {
|
||||
system_id: systemId,
|
||||
routes_settings: routesSettings,
|
||||
},
|
||||
});
|
||||
},
|
||||
[outCommand],
|
||||
);
|
||||
|
||||
const { loading, setLoading } = useLoadRoutes({
|
||||
data: settingsRoutes,
|
||||
hubs: hubs,
|
||||
loadRoutesCommand,
|
||||
routesList: routes,
|
||||
deps: [pings],
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
update({ loadingPublicRoutes: loading });
|
||||
}, [loading, update]);
|
||||
|
||||
useMapEventListener(event => {
|
||||
if (event.name === Commands.routes) {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -23,10 +23,13 @@ import {
|
||||
Commands,
|
||||
MapHandlers,
|
||||
CommandCommentRemoved,
|
||||
CommandPingAdded,
|
||||
CommandPingCancelled,
|
||||
} from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
|
||||
import {
|
||||
useCommandComments,
|
||||
useCommandPings,
|
||||
useCommandsCharacters,
|
||||
useCommandsConnections,
|
||||
useCommandsSystems,
|
||||
@@ -57,6 +60,7 @@ export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
|
||||
const mapRoutes = useRoutes();
|
||||
const mapUserRoutes = useUserRoutes();
|
||||
const { addComment, removeComment } = useCommandComments();
|
||||
const { pingAdded, pingCancelled } = useCommandPings();
|
||||
const { characterActivityData, trackingCharactersData, userSettingsUpdated } = useCommandsActivity();
|
||||
|
||||
useImperativeHandle(
|
||||
@@ -163,6 +167,14 @@ export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
|
||||
removeComment(data as CommandCommentRemoved);
|
||||
break;
|
||||
|
||||
case Commands.pingAdded:
|
||||
pingAdded(data as CommandPingAdded);
|
||||
break;
|
||||
|
||||
case Commands.pingCancelled:
|
||||
pingCancelled(data as CommandPingCancelled);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn(`JOipP Interface handlers: Unknown command: ${type}`, data);
|
||||
break;
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from '@/hooks/Mapper/components/mapInterface/constants.tsx';
|
||||
import { WindowProps } from '@/hooks/Mapper/components/ui-kit/WindowManager/types.ts';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { SNAP_GAP, WindowsManagerOnChange } from '@/hooks/Mapper/components/ui-kit/WindowManager';
|
||||
import { /*SNAP_GAP,*/ WindowsManagerOnChange } from '@/hooks/Mapper/components/ui-kit/WindowManager';
|
||||
|
||||
export type StoredWindowProps = Omit<WindowProps, 'content'>;
|
||||
export type WindowStoreInfo = {
|
||||
@@ -17,7 +17,7 @@ export type WindowStoreInfo = {
|
||||
visible: WidgetsIds[];
|
||||
viewPort?: { w: number; h: number } | undefined;
|
||||
};
|
||||
export type UpdateWidgetSettingsFunc = (widgets: WindowProps[]) => void;
|
||||
// export type UpdateWidgetSettingsFunc = (widgets: WindowProps[]) => void;
|
||||
export type ToggleWidgetVisibility = (widgetId: WidgetsIds) => void;
|
||||
|
||||
export const getDefaultWidgetProps = () => ({
|
||||
@@ -66,7 +66,7 @@ export const useStoreWidgets = () => {
|
||||
...x,
|
||||
windows: windows.map(wnd => {
|
||||
if (wnd.id === widgetId) {
|
||||
return { ...wnd, position: { x: SNAP_GAP, y: SNAP_GAP }, zIndex: maxZIndex + 1 };
|
||||
return { ...wnd, /*position: { x: SNAP_GAP, y: SNAP_GAP },*/ zIndex: maxZIndex + 1 };
|
||||
}
|
||||
|
||||
return wnd;
|
||||
|
||||
@@ -3,15 +3,31 @@ export enum AvailableThemes {
|
||||
pathfinder = 'pathfinder',
|
||||
}
|
||||
|
||||
export enum MiniMapPlacement {
|
||||
rightTop = 'rightTop',
|
||||
rightBottom = 'rightBottom',
|
||||
leftTop = 'leftTop',
|
||||
leftBottom = 'leftBottom',
|
||||
hide = 'hide',
|
||||
}
|
||||
|
||||
export enum PingsPlacement {
|
||||
rightTop = 'rightTop',
|
||||
rightBottom = 'rightBottom',
|
||||
leftTop = 'leftTop',
|
||||
leftBottom = 'leftBottom',
|
||||
}
|
||||
|
||||
export type InterfaceStoredSettings = {
|
||||
isShowMenu: boolean;
|
||||
isShowMinimap: boolean;
|
||||
isShowKSpace: boolean;
|
||||
isThickConnections: boolean;
|
||||
isShowUnsplashedSignatures: boolean;
|
||||
isShowBackgroundPattern: boolean;
|
||||
isSoftBackground: boolean;
|
||||
theme: AvailableThemes;
|
||||
minimapPlacement: MiniMapPlacement;
|
||||
pingsPlacement: PingsPlacement;
|
||||
};
|
||||
|
||||
export type RoutesType = {
|
||||
|
||||
@@ -8,3 +8,4 @@ export * from './signatures';
|
||||
export * from './connectionPassages';
|
||||
export * from './permissions';
|
||||
export * from './comment';
|
||||
export * from './ping';
|
||||
|
||||
@@ -4,7 +4,7 @@ import { WormholeDataRaw } from '@/hooks/Mapper/types/wormholes.ts';
|
||||
import { ActivitySummary, CharacterTypeRaw, TrackingCharacter } from '@/hooks/Mapper/types/character.ts';
|
||||
import { RoutesList } from '@/hooks/Mapper/types/routes.ts';
|
||||
import { DetailedKill, Kill } from '@/hooks/Mapper/types/kills.ts';
|
||||
import { CommentType, SystemSignature, UserPermissions } from '@/hooks/Mapper/types';
|
||||
import { CommentType, PingData, SystemSignature, UserPermissions } from '@/hooks/Mapper/types';
|
||||
|
||||
export enum Commands {
|
||||
init = 'init',
|
||||
@@ -37,6 +37,8 @@ export enum Commands {
|
||||
updateTracking = 'update_tracking',
|
||||
userSettingsUpdated = 'user_settings_updated',
|
||||
showTracking = 'show_tracking',
|
||||
pingAdded = 'ping_added',
|
||||
pingCancelled = 'ping_cancelled',
|
||||
}
|
||||
|
||||
export type Command =
|
||||
@@ -69,7 +71,9 @@ export type Command =
|
||||
| Commands.userSettingsUpdated
|
||||
| Commands.updateActivity
|
||||
| Commands.updateTracking
|
||||
| Commands.showTracking;
|
||||
| Commands.showTracking
|
||||
| Commands.pingAdded
|
||||
| Commands.pingCancelled;
|
||||
|
||||
export type CommandInit = {
|
||||
systems: SolarSystemRawType[];
|
||||
@@ -78,6 +82,8 @@ export type CommandInit = {
|
||||
system_static_infos: SolarSystemStaticInfoRaw[];
|
||||
connections: SolarSystemConnection[];
|
||||
wormholes: WormholeDataRaw[];
|
||||
|
||||
// TODO WHY HERE ANY?!!?!?
|
||||
effects: any[];
|
||||
characters: CharacterTypeRaw[];
|
||||
present_characters: string[];
|
||||
@@ -143,6 +149,8 @@ export type CommandUpdateTracking = {
|
||||
track: boolean;
|
||||
follow: boolean;
|
||||
};
|
||||
export type CommandPingAdded = PingData[];
|
||||
export type CommandPingCancelled = Pick<PingData, 'type' | 'solar_system_id'>;
|
||||
|
||||
export interface UserSettings {
|
||||
primaryCharacterId?: string;
|
||||
@@ -190,6 +198,8 @@ export interface CommandData {
|
||||
[Commands.systemCommentRemoved]: CommandCommentRemoved;
|
||||
[Commands.systemCommentsUpdated]: unknown;
|
||||
[Commands.showTracking]: CommandShowTracking;
|
||||
[Commands.pingAdded]: CommandPingAdded;
|
||||
[Commands.pingCancelled]: CommandPingCancelled;
|
||||
}
|
||||
|
||||
export interface MapHandlers {
|
||||
@@ -242,13 +252,14 @@ export enum OutCommand {
|
||||
addSystemComment = 'addSystemComment',
|
||||
deleteSystemComment = 'deleteSystemComment',
|
||||
getSystemComments = 'getSystemComments',
|
||||
// toggleTrack = 'toggle_track',
|
||||
toggleFollow = 'toggle_follow',
|
||||
getCharacterInfo = 'getCharacterInfo',
|
||||
getCharactersTrackingInfo = 'getCharactersTrackingInfo',
|
||||
updateCharacterTracking = 'updateCharacterTracking',
|
||||
updateFollowingCharacter = 'updateFollowingCharacter',
|
||||
updateMainCharacter = 'updateMainCharacter',
|
||||
addPing = 'add_ping',
|
||||
cancelPing = 'cancel_ping',
|
||||
|
||||
// Only UI commands
|
||||
openSettings = 'open_settings',
|
||||
@@ -258,7 +269,7 @@ export enum OutCommand {
|
||||
updateUserSettings = 'update_user_settings',
|
||||
unlinkSignature = 'unlink_signature',
|
||||
searchSystems = 'search_systems',
|
||||
undoDeleteSignatures = 'undo_delete_signatures',
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type OutCommandHandler = <T = any>(event: { type: OutCommand; data: any }) => Promise<T>;
|
||||
export type OutCommandHandler = <T = unknown>(event: { type: OutCommand; data: unknown }) => Promise<T>;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { CharacterTypeRaw } from '@/hooks/Mapper/types/character.ts';
|
||||
import { SolarSystemRawType } from '@/hooks/Mapper/types/system.ts';
|
||||
import { RoutesList } from '@/hooks/Mapper/types/routes.ts';
|
||||
import { SolarSystemConnection } from '@/hooks/Mapper/types/connection.ts';
|
||||
import { UserPermissions } from '@/hooks/Mapper/types';
|
||||
import { PingData, UserPermissions } from '@/hooks/Mapper/types';
|
||||
import { SystemSignature } from '@/hooks/Mapper/types/signatures';
|
||||
|
||||
export type MapUnionTypes = {
|
||||
@@ -28,4 +28,5 @@ export type MapUnionTypes = {
|
||||
|
||||
mainCharacterEveId: string | null;
|
||||
followingCharacterEveId: string | null;
|
||||
pings: PingData[];
|
||||
};
|
||||
|
||||
12
assets/js/hooks/Mapper/types/ping.ts
Normal file
12
assets/js/hooks/Mapper/types/ping.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export enum PingType {
|
||||
Alert,
|
||||
Rally,
|
||||
}
|
||||
|
||||
export type PingData = {
|
||||
inserted_at: number;
|
||||
character_eve_id: string;
|
||||
solar_system_id: string;
|
||||
message: string;
|
||||
type: PingType;
|
||||
};
|
||||
@@ -30,6 +30,7 @@ export type GroupType = {
|
||||
export type SignatureCustomInfo = {
|
||||
k162Type?: string;
|
||||
isEOL?: boolean;
|
||||
isCrit?: boolean;
|
||||
};
|
||||
|
||||
export type SystemSignature = {
|
||||
@@ -46,6 +47,7 @@ export type SystemSignature = {
|
||||
linked_system?: SolarSystemStaticInfoRaw;
|
||||
inserted_at?: string;
|
||||
updated_at?: string;
|
||||
deleted?: boolean;
|
||||
};
|
||||
|
||||
export interface ExtendedSystemSignature extends SystemSignature {
|
||||
@@ -53,6 +55,7 @@ export interface ExtendedSystemSignature extends SystemSignature {
|
||||
pendingAddition?: boolean;
|
||||
pendingUntil?: number;
|
||||
finalTimeoutId?: number;
|
||||
deleted?: boolean;
|
||||
}
|
||||
|
||||
export enum SignatureKindENG {
|
||||
@@ -75,6 +78,26 @@ export enum SignatureKindRU {
|
||||
Starbase = 'Starbase',
|
||||
}
|
||||
|
||||
export enum SignatureKindFR {
|
||||
CosmicSignature = 'Signature cosmique (type)',
|
||||
CosmicAnomaly = 'Anomalie cosmique',
|
||||
Structure = 'Structure',
|
||||
Ship = 'Vaisseau',
|
||||
Deployable = 'Déployable',
|
||||
Drone = 'Drone',
|
||||
Starbase = 'Base stellaire',
|
||||
}
|
||||
|
||||
export enum SignatureKindDE {
|
||||
CosmicSignature = 'Kosmische Signatur (typ)',
|
||||
CosmicAnomaly = 'Kosmische Anomalie',
|
||||
Structure = 'Struktur',
|
||||
Ship = 'Schiff',
|
||||
Deployable = 'Mobile Struktur',
|
||||
Drone = 'Drohne',
|
||||
Starbase = 'Sternenbasis',
|
||||
}
|
||||
|
||||
export enum SignatureGroupENG {
|
||||
CosmicSignature = 'Cosmic Signature',
|
||||
Wormhole = 'Wormhole',
|
||||
@@ -94,3 +117,23 @@ export enum SignatureGroupRU {
|
||||
OreSite = 'Астероидный район',
|
||||
CombatSite = 'Боевой район',
|
||||
}
|
||||
|
||||
export enum SignatureGroupFR {
|
||||
CosmicSignature = 'Signature cosmique (groupe)',
|
||||
Wormhole = 'Trou de ver',
|
||||
GasSite = 'Site de gaz',
|
||||
RelicSite = 'Site de reliques',
|
||||
DataSite = 'Site de données',
|
||||
OreSite = 'Site de minerai',
|
||||
CombatSite = 'Site de combat',
|
||||
}
|
||||
|
||||
export enum SignatureGroupDE {
|
||||
CosmicSignature = 'Kosmische Signatur (gruppe)',
|
||||
Wormhole = 'Wurmloch',
|
||||
GasSite = 'Gasgebiet',
|
||||
RelicSite = 'Reliktgebiet',
|
||||
DataSite = 'Datengebiet',
|
||||
OreSite = 'Mineraliengebiet',
|
||||
CombatSite = 'Kampfgebiet',
|
||||
}
|
||||
|
||||
BIN
assets/static/images/news/05-08-undo/undo.png
Executable file
BIN
assets/static/images/news/05-08-undo/undo.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
@@ -28,5 +28,6 @@ defmodule WandererApp.Api do
|
||||
resource WandererApp.Api.UserTransaction
|
||||
resource WandererApp.Api.CorpWalletTransaction
|
||||
resource WandererApp.Api.License
|
||||
resource WandererApp.Api.MapPing
|
||||
end
|
||||
end
|
||||
|
||||
@@ -85,7 +85,7 @@ defmodule WandererApp.Api.Character do
|
||||
|
||||
update :update do
|
||||
require_atomic? false
|
||||
accept([:access_token, :refresh_token, :expires_at, :scopes])
|
||||
accept([:name, :access_token, :refresh_token, :expires_at, :scopes])
|
||||
|
||||
change(set_attribute(:deleted, false))
|
||||
end
|
||||
|
||||
@@ -3,17 +3,19 @@ defmodule WandererApp.Api.MapCharacterSettings do
|
||||
|
||||
use Ash.Resource,
|
||||
domain: WandererApp.Api,
|
||||
data_layer: AshPostgres.DataLayer
|
||||
data_layer: AshPostgres.DataLayer,
|
||||
extensions: [AshCloak]
|
||||
|
||||
@derive {Jason.Encoder, only: [
|
||||
:id,
|
||||
:map_id,
|
||||
:character_id,
|
||||
:tracked,
|
||||
:followed,
|
||||
:inserted_at,
|
||||
:updated_at
|
||||
]}
|
||||
@derive {Jason.Encoder,
|
||||
only: [
|
||||
:id,
|
||||
:map_id,
|
||||
:character_id,
|
||||
:tracked,
|
||||
:followed,
|
||||
:inserted_at,
|
||||
:updated_at
|
||||
]}
|
||||
|
||||
postgres do
|
||||
repo(WandererApp.Repo)
|
||||
@@ -23,8 +25,10 @@ defmodule WandererApp.Api.MapCharacterSettings do
|
||||
code_interface do
|
||||
define(:create, action: :create)
|
||||
define(:destroy, action: :destroy)
|
||||
define(:update, action: :update)
|
||||
|
||||
define(:read_by_map, action: :read_by_map)
|
||||
define(:read_by_map_and_character, action: :read_by_map_and_character)
|
||||
define(:by_map_filtered, action: :by_map_filtered)
|
||||
define(:tracked_by_map_filtered, action: :tracked_by_map_filtered)
|
||||
define(:tracked_by_character, action: :tracked_by_character)
|
||||
@@ -44,7 +48,31 @@ defmodule WandererApp.Api.MapCharacterSettings do
|
||||
:tracked
|
||||
]
|
||||
|
||||
defaults [:create, :read, :update, :destroy]
|
||||
defaults [:read, :destroy]
|
||||
|
||||
create :create do
|
||||
primary? true
|
||||
upsert? true
|
||||
upsert_identity :uniq_map_character
|
||||
|
||||
upsert_fields [
|
||||
:map_id,
|
||||
:character_id
|
||||
]
|
||||
|
||||
accept [
|
||||
:map_id,
|
||||
:character_id,
|
||||
:tracked,
|
||||
:followed
|
||||
]
|
||||
|
||||
argument :map_id, :uuid, allow_nil?: false
|
||||
argument :character_id, :uuid, allow_nil?: false
|
||||
|
||||
change manage_relationship(:map_id, :map, on_lookup: :relate, on_no_match: nil)
|
||||
change manage_relationship(:character_id, :character, on_lookup: :relate, on_no_match: nil)
|
||||
end
|
||||
|
||||
read :by_map_filtered do
|
||||
argument(:map_id, :string, allow_nil?: false)
|
||||
@@ -67,6 +95,15 @@ defmodule WandererApp.Api.MapCharacterSettings do
|
||||
filter(expr(map_id == ^arg(:map_id)))
|
||||
end
|
||||
|
||||
read :read_by_map_and_character do
|
||||
get? true
|
||||
|
||||
argument(:map_id, :string, allow_nil?: false)
|
||||
argument(:character_id, :uuid, allow_nil?: false)
|
||||
|
||||
filter(expr(map_id == ^arg(:map_id) and character_id == ^arg(:character_id)))
|
||||
end
|
||||
|
||||
read :tracked_by_map_all do
|
||||
argument(:map_id, :string, allow_nil?: false)
|
||||
filter(expr(map_id == ^arg(:map_id) and tracked == true))
|
||||
@@ -77,6 +114,20 @@ defmodule WandererApp.Api.MapCharacterSettings do
|
||||
filter(expr(character_id == ^arg(:character_id) and tracked == true))
|
||||
end
|
||||
|
||||
update :update do
|
||||
primary? true
|
||||
require_atomic? false
|
||||
|
||||
accept([
|
||||
:ship,
|
||||
:ship_name,
|
||||
:ship_item_id,
|
||||
:solar_system_id,
|
||||
:structure_id,
|
||||
:station_id
|
||||
])
|
||||
end
|
||||
|
||||
update :track do
|
||||
accept [:map_id, :character_id]
|
||||
argument :map_id, :string, allow_nil?: false
|
||||
@@ -134,6 +185,28 @@ defmodule WandererApp.Api.MapCharacterSettings do
|
||||
end
|
||||
end
|
||||
|
||||
cloak do
|
||||
vault(WandererApp.Vault)
|
||||
|
||||
attributes([
|
||||
:ship,
|
||||
:ship_name,
|
||||
:ship_item_id,
|
||||
:solar_system_id,
|
||||
:structure_id,
|
||||
:station_id
|
||||
])
|
||||
|
||||
decrypt_by_default([
|
||||
:ship,
|
||||
:ship_name,
|
||||
:ship_item_id,
|
||||
:solar_system_id,
|
||||
:structure_id,
|
||||
:station_id
|
||||
])
|
||||
end
|
||||
|
||||
attributes do
|
||||
uuid_primary_key :id
|
||||
|
||||
@@ -147,6 +220,13 @@ defmodule WandererApp.Api.MapCharacterSettings do
|
||||
allow_nil? true
|
||||
end
|
||||
|
||||
attribute :solar_system_id, :integer
|
||||
attribute :structure_id, :integer
|
||||
attribute :station_id, :integer
|
||||
attribute :ship, :integer
|
||||
attribute :ship_name, :string
|
||||
attribute :ship_item_id, :integer
|
||||
|
||||
create_timestamp(:inserted_at)
|
||||
update_timestamp(:updated_at)
|
||||
end
|
||||
|
||||
@@ -38,7 +38,8 @@ defmodule WandererApp.Api.MapConnection do
|
||||
:map_id,
|
||||
:solar_system_source,
|
||||
:solar_system_target,
|
||||
:type
|
||||
:type,
|
||||
:ship_size_type
|
||||
]
|
||||
|
||||
defaults [:create, :read, :update, :destroy]
|
||||
|
||||
116
lib/wanderer_app/api/map_ping.ex
Normal file
116
lib/wanderer_app/api/map_ping.ex
Normal file
@@ -0,0 +1,116 @@
|
||||
defmodule WandererApp.Api.MapPing do
|
||||
@moduledoc false
|
||||
|
||||
use Ash.Resource,
|
||||
domain: WandererApp.Api,
|
||||
data_layer: AshPostgres.DataLayer
|
||||
|
||||
postgres do
|
||||
repo(WandererApp.Repo)
|
||||
table("map_pings_v1")
|
||||
end
|
||||
|
||||
code_interface do
|
||||
define(:new, action: :new)
|
||||
define(:destroy, action: :destroy)
|
||||
|
||||
define(:by_map,
|
||||
action: :by_map
|
||||
)
|
||||
|
||||
define(:by_map_and_system,
|
||||
action: :by_map_and_system
|
||||
)
|
||||
|
||||
define(:by_inserted_before, action: :by_inserted_before, args: [:inserted_before])
|
||||
end
|
||||
|
||||
actions do
|
||||
default_accept [
|
||||
:type,
|
||||
:message
|
||||
]
|
||||
|
||||
defaults [:read, :update, :destroy]
|
||||
|
||||
create :new do
|
||||
accept [
|
||||
:map_id,
|
||||
:system_id,
|
||||
:character_id,
|
||||
:type,
|
||||
:message
|
||||
]
|
||||
|
||||
primary?(true)
|
||||
|
||||
argument :map_id, :uuid, allow_nil?: false
|
||||
argument :system_id, :uuid, allow_nil?: false
|
||||
argument :character_id, :uuid, allow_nil?: false
|
||||
|
||||
change manage_relationship(:map_id, :map, on_lookup: :relate, on_no_match: nil)
|
||||
change manage_relationship(:system_id, :system, on_lookup: :relate, on_no_match: nil)
|
||||
change manage_relationship(:character_id, :character, on_lookup: :relate, on_no_match: nil)
|
||||
end
|
||||
|
||||
read :by_map do
|
||||
argument(:map_id, :string, allow_nil?: false)
|
||||
|
||||
filter(expr(map_id == ^arg(:map_id)))
|
||||
end
|
||||
|
||||
read :by_map_and_system do
|
||||
argument(:map_id, :string, allow_nil?: false)
|
||||
argument(:system_id, :string, allow_nil?: false)
|
||||
|
||||
filter(expr(map_id == ^arg(:map_id) and system_id == ^arg(:system_id)))
|
||||
end
|
||||
|
||||
read :by_inserted_before do
|
||||
argument(:inserted_before, :utc_datetime, allow_nil?: false)
|
||||
|
||||
filter(expr(inserted_at <= ^arg(:inserted_before)))
|
||||
end
|
||||
end
|
||||
|
||||
attributes do
|
||||
uuid_primary_key :id
|
||||
|
||||
# ping: 0
|
||||
# rally_point: 1
|
||||
attribute :type, :integer do
|
||||
default 0
|
||||
|
||||
allow_nil? true
|
||||
end
|
||||
|
||||
attribute :message, :string do
|
||||
allow_nil? true
|
||||
end
|
||||
|
||||
create_timestamp(:inserted_at)
|
||||
update_timestamp(:updated_at)
|
||||
end
|
||||
|
||||
relationships do
|
||||
belongs_to :map, WandererApp.Api.Map do
|
||||
attribute_writable? true
|
||||
end
|
||||
|
||||
belongs_to :system, WandererApp.Api.MapSystem do
|
||||
attribute_writable? true
|
||||
end
|
||||
|
||||
belongs_to :character, WandererApp.Api.Character do
|
||||
attribute_writable? true
|
||||
end
|
||||
end
|
||||
|
||||
postgres do
|
||||
references do
|
||||
reference :map, on_delete: :delete
|
||||
reference :system, on_delete: :delete
|
||||
reference :character, on_delete: :delete
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -24,7 +24,19 @@ defmodule WandererApp.Api.MapSystemSignature do
|
||||
)
|
||||
|
||||
define(:by_system_id, action: :by_system_id, args: [:system_id])
|
||||
define(:by_system_id_all, action: :by_system_id_all, args: [:system_id])
|
||||
|
||||
define(:by_system_id_and_eve_ids,
|
||||
action: :by_system_id_and_eve_ids,
|
||||
args: [:system_id, :eve_ids]
|
||||
)
|
||||
|
||||
define(:by_linked_system_id, action: :by_linked_system_id, args: [:linked_system_id])
|
||||
|
||||
define(:by_deleted_and_updated_before!,
|
||||
action: :by_deleted_and_updated_before,
|
||||
args: [:deleted, :updated_before]
|
||||
)
|
||||
end
|
||||
|
||||
actions do
|
||||
@@ -36,7 +48,8 @@ defmodule WandererApp.Api.MapSystemSignature do
|
||||
:description,
|
||||
:kind,
|
||||
:group,
|
||||
:type
|
||||
:type,
|
||||
:deleted
|
||||
]
|
||||
|
||||
defaults [:read, :destroy]
|
||||
@@ -64,7 +77,8 @@ defmodule WandererApp.Api.MapSystemSignature do
|
||||
:kind,
|
||||
:group,
|
||||
:type,
|
||||
:custom_info
|
||||
:custom_info,
|
||||
:deleted
|
||||
]
|
||||
|
||||
argument :system_id, :uuid, allow_nil?: false
|
||||
@@ -83,7 +97,8 @@ defmodule WandererApp.Api.MapSystemSignature do
|
||||
:group,
|
||||
:type,
|
||||
:custom_info,
|
||||
:updated
|
||||
:deleted,
|
||||
:update_forced_at
|
||||
]
|
||||
|
||||
primary? true
|
||||
@@ -105,14 +120,32 @@ defmodule WandererApp.Api.MapSystemSignature do
|
||||
read :by_system_id do
|
||||
argument(:system_id, :string, allow_nil?: false)
|
||||
|
||||
filter(expr(system_id == ^arg(:system_id) and deleted == false))
|
||||
end
|
||||
|
||||
read :by_system_id_all do
|
||||
argument(:system_id, :string, allow_nil?: false)
|
||||
filter(expr(system_id == ^arg(:system_id)))
|
||||
end
|
||||
|
||||
read :by_system_id_and_eve_ids do
|
||||
argument(:system_id, :string, allow_nil?: false)
|
||||
argument(:eve_ids, {:array, :string}, allow_nil?: false)
|
||||
filter(expr(system_id == ^arg(:system_id) and eve_id in ^arg(:eve_ids)))
|
||||
end
|
||||
|
||||
read :by_linked_system_id do
|
||||
argument(:linked_system_id, :integer, allow_nil?: false)
|
||||
|
||||
filter(expr(linked_system_id == ^arg(:linked_system_id)))
|
||||
end
|
||||
|
||||
read :by_deleted_and_updated_before do
|
||||
argument(:deleted, :boolean, allow_nil?: false)
|
||||
argument(:updated_before, :utc_datetime, allow_nil?: false)
|
||||
|
||||
filter(expr(deleted == ^arg(:deleted) and updated_at < ^arg(:updated_before)))
|
||||
end
|
||||
end
|
||||
|
||||
attributes do
|
||||
@@ -149,7 +182,14 @@ defmodule WandererApp.Api.MapSystemSignature do
|
||||
allow_nil? true
|
||||
end
|
||||
|
||||
attribute :updated, :integer
|
||||
attribute :deleted, :boolean do
|
||||
allow_nil? false
|
||||
default false
|
||||
end
|
||||
|
||||
attribute :update_forced_at, :utc_datetime do
|
||||
allow_nil? true
|
||||
end
|
||||
|
||||
create_timestamp(:inserted_at)
|
||||
update_timestamp(:updated_at)
|
||||
@@ -166,21 +206,20 @@ defmodule WandererApp.Api.MapSystemSignature do
|
||||
end
|
||||
|
||||
@derive {Jason.Encoder,
|
||||
only: [
|
||||
:id,
|
||||
:system_id,
|
||||
:eve_id,
|
||||
:character_eve_id,
|
||||
:name,
|
||||
:description,
|
||||
:type,
|
||||
:linked_system_id,
|
||||
:kind,
|
||||
:group,
|
||||
:custom_info,
|
||||
:updated,
|
||||
:inserted_at,
|
||||
:updated_at
|
||||
]
|
||||
}
|
||||
only: [
|
||||
:id,
|
||||
:system_id,
|
||||
:eve_id,
|
||||
:character_eve_id,
|
||||
:name,
|
||||
:description,
|
||||
:type,
|
||||
:linked_system_id,
|
||||
:kind,
|
||||
:group,
|
||||
:custom_info,
|
||||
:deleted,
|
||||
:inserted_at,
|
||||
:updated_at
|
||||
]}
|
||||
end
|
||||
|
||||
@@ -112,6 +112,8 @@ defmodule WandererApp.Api.UserActivity do
|
||||
:map_connection_added,
|
||||
:map_connection_updated,
|
||||
:map_connection_removed,
|
||||
:map_rally_added,
|
||||
:map_rally_cancelled,
|
||||
:signatures_added,
|
||||
:signatures_removed
|
||||
]
|
||||
|
||||
@@ -39,40 +39,49 @@ defmodule WandererApp.CachedInfo do
|
||||
def get_system_static_info(solar_system_id) do
|
||||
case Cachex.get(:system_static_info_cache, solar_system_id) do
|
||||
{:ok, nil} ->
|
||||
{:ok, systems} = WandererApp.Api.MapSolarSystem.read()
|
||||
case WandererApp.Api.MapSolarSystem.read() do
|
||||
{:ok, systems} ->
|
||||
systems
|
||||
|> Enum.each(fn system ->
|
||||
Cachex.put(
|
||||
:system_static_info_cache,
|
||||
system.solar_system_id,
|
||||
Map.take(system, [
|
||||
:solar_system_id,
|
||||
:region_id,
|
||||
:constellation_id,
|
||||
:solar_system_name,
|
||||
:solar_system_name_lc,
|
||||
:constellation_name,
|
||||
:region_name,
|
||||
:system_class,
|
||||
:security,
|
||||
:type_description,
|
||||
:class_title,
|
||||
:is_shattered,
|
||||
:effect_name,
|
||||
:effect_power,
|
||||
:statics,
|
||||
:wandering,
|
||||
:triglavian_invasion_status,
|
||||
:sun_type_id
|
||||
])
|
||||
)
|
||||
end)
|
||||
|
||||
systems
|
||||
|> Enum.each(fn system ->
|
||||
Cachex.put(
|
||||
:system_static_info_cache,
|
||||
system.solar_system_id,
|
||||
Map.take(system, [
|
||||
:solar_system_id,
|
||||
:region_id,
|
||||
:constellation_id,
|
||||
:solar_system_name,
|
||||
:solar_system_name_lc,
|
||||
:constellation_name,
|
||||
:region_name,
|
||||
:system_class,
|
||||
:security,
|
||||
:type_description,
|
||||
:class_title,
|
||||
:is_shattered,
|
||||
:effect_name,
|
||||
:effect_power,
|
||||
:statics,
|
||||
:wandering,
|
||||
:triglavian_invasion_status,
|
||||
:sun_type_id
|
||||
])
|
||||
)
|
||||
end)
|
||||
Cachex.get(:system_static_info_cache, solar_system_id)
|
||||
|
||||
Cachex.get(:system_static_info_cache, solar_system_id)
|
||||
{:error, reason} ->
|
||||
Logger.error("Failed to read solar systems from API: #{inspect(reason)}")
|
||||
{:error, :api_error}
|
||||
end
|
||||
|
||||
{:ok, system_static_info} ->
|
||||
{:ok, system_static_info}
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.error("Failed to get system static info from cache: #{inspect(reason)}")
|
||||
{:error, :cache_error}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -7,6 +7,15 @@ defmodule WandererApp.Character do
|
||||
@read_character_wallet_scope "esi-wallet.read_character_wallet.v1"
|
||||
@read_corp_wallet_scope "esi-wallet.read_corporation_wallets.v1"
|
||||
|
||||
@default_character_tracking_data %{
|
||||
solar_system_id: nil,
|
||||
structure_id: nil,
|
||||
station_id: nil,
|
||||
ship: nil,
|
||||
ship_name: nil,
|
||||
ship_item_id: nil
|
||||
}
|
||||
|
||||
@decorate cacheable(
|
||||
cache: WandererApp.Cache,
|
||||
key: "characters-#{character_eve_id}"
|
||||
@@ -45,6 +54,32 @@ defmodule WandererApp.Character do
|
||||
end
|
||||
end
|
||||
|
||||
def get_map_character(map_id, character_id) do
|
||||
case get_character(character_id) do
|
||||
{:ok, character} ->
|
||||
{:ok,
|
||||
character
|
||||
|> maybe_merge_map_character_settings(
|
||||
map_id,
|
||||
WandererApp.Character.TrackerManager.Impl.character_is_present(map_id, character_id)
|
||||
)}
|
||||
|
||||
_ ->
|
||||
{:ok, nil}
|
||||
end
|
||||
end
|
||||
|
||||
def get_map_character!(map_id, character_id) do
|
||||
case get_map_character(map_id, character_id) do
|
||||
{:ok, character} ->
|
||||
character
|
||||
|
||||
_ ->
|
||||
Logger.error("Failed to get map character #{map_id} #{character_id}")
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def get_character_eve_ids!(character_ids),
|
||||
do:
|
||||
character_ids
|
||||
@@ -146,7 +181,7 @@ defmodule WandererApp.Character do
|
||||
params: opts[:params]
|
||||
) do
|
||||
{:ok, result} ->
|
||||
{:ok, result |> _prepare_search_results()}
|
||||
{:ok, result |> prepare_search_results()}
|
||||
|
||||
{:error, error} ->
|
||||
Logger.warning("#{__MODULE__} failed search: #{inspect(error)}")
|
||||
@@ -208,7 +243,28 @@ defmodule WandererApp.Character do
|
||||
end
|
||||
end
|
||||
|
||||
defp _prepare_search_results(result) do
|
||||
defp maybe_merge_map_character_settings(character, map_id, true), do: character
|
||||
|
||||
defp maybe_merge_map_character_settings(
|
||||
%{id: character_id} = character,
|
||||
map_id,
|
||||
_character_is_present
|
||||
) do
|
||||
WandererApp.MapCharacterSettingsRepo.get(map_id, character_id)
|
||||
|> case do
|
||||
{:ok, settings} when not is_nil(settings) ->
|
||||
character
|
||||
|> Map.put(:online, false)
|
||||
|> Map.merge(settings)
|
||||
|
||||
_ ->
|
||||
character
|
||||
|> Map.put(:online, false)
|
||||
|> Map.merge(@default_character_tracking_data)
|
||||
end
|
||||
end
|
||||
|
||||
defp prepare_search_results(result) do
|
||||
{:ok, characters} =
|
||||
_load_eve_info(Map.get(result, "character"), :get_character_info, &_map_character_info/1)
|
||||
|
||||
|
||||
@@ -139,6 +139,13 @@ defmodule WandererApp.Character.Tracker do
|
||||
|
||||
{:error, error} ->
|
||||
Logger.error("#{__MODULE__} failed to update_ship: #{inspect(error)}")
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:ship_forbidden",
|
||||
true,
|
||||
ttl: @forbidden_ttl
|
||||
)
|
||||
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
@@ -191,6 +198,13 @@ defmodule WandererApp.Character.Tracker do
|
||||
|
||||
{:error, error} ->
|
||||
Logger.error("#{__MODULE__} failed to update_location: #{inspect(error)}")
|
||||
|
||||
WandererApp.Cache.put(
|
||||
"character:#{character_id}:location_forbidden",
|
||||
true,
|
||||
ttl: @forbidden_ttl
|
||||
)
|
||||
|
||||
{:error, error}
|
||||
end
|
||||
|
||||
@@ -305,16 +319,12 @@ defmodule WandererApp.Character.Tracker do
|
||||
duration = DateTime.diff(DateTime.utc_now(), error_time, :second)
|
||||
|
||||
if duration >= @online_error_timeout do
|
||||
{:ok, character_state} = WandererApp.Character.get_character_state(character_id)
|
||||
WandererApp.Cache.delete("character:#{character_id}:online_forbidden")
|
||||
WandererApp.Cache.delete("character:#{character_id}:online_error_time")
|
||||
WandererApp.Character.update_character(character_id, %{online: false})
|
||||
# WandererApp.Cache.delete("character:#{character_id}:location_started")
|
||||
# WandererApp.Cache.delete("character:#{character_id}:start_solar_system_id")
|
||||
|
||||
WandererApp.Character.update_character_state(character_id, %{
|
||||
character_state
|
||||
| is_online: false
|
||||
is_online: false
|
||||
})
|
||||
|
||||
:ok
|
||||
|
||||
@@ -13,7 +13,7 @@ defmodule WandererApp.Character.TrackerManager.Impl do
|
||||
}
|
||||
|
||||
@garbage_collection_interval :timer.minutes(15)
|
||||
@untrack_characters_interval :timer.minutes(5)
|
||||
@untrack_characters_interval :timer.minutes(1)
|
||||
@inactive_character_timeout :timer.minutes(5)
|
||||
|
||||
@logger Application.compile_env(:wanderer_app, :logger)
|
||||
@@ -34,6 +34,7 @@ defmodule WandererApp.Character.TrackerManager.Impl do
|
||||
|
||||
def start(state) do
|
||||
{:ok, tracked_characters} = WandererApp.Cache.lookup("tracked_characters", [])
|
||||
WandererApp.Cache.insert("tracked_characters", [])
|
||||
|
||||
tracked_characters
|
||||
|> Enum.each(fn character_id ->
|
||||
@@ -51,6 +52,12 @@ defmodule WandererApp.Character.TrackerManager.Impl do
|
||||
tracked_characters = [character_id | characters] |> Enum.uniq()
|
||||
WandererApp.Cache.insert("tracked_characters", tracked_characters)
|
||||
|
||||
WandererApp.Character.update_character(character_id, %{online: false})
|
||||
|
||||
WandererApp.Character.update_character_state(character_id, %{
|
||||
is_online: false
|
||||
})
|
||||
|
||||
WandererApp.Character.TrackerPoolDynamicSupervisor.start_tracking(character_id)
|
||||
|
||||
WandererApp.TaskWrapper.start_link(WandererApp.Character, :update_character_state, [
|
||||
@@ -213,6 +220,18 @@ defmodule WandererApp.Character.TrackerManager.Impl do
|
||||
track: false
|
||||
})
|
||||
|
||||
{:ok, character} = WandererApp.Character.get_character(character_id)
|
||||
|
||||
{:ok, _updated} =
|
||||
WandererApp.MapCharacterSettingsRepo.update(map_id, character_id, %{
|
||||
ship: character.ship,
|
||||
ship_name: character.ship_name,
|
||||
ship_item_id: character.ship_item_id,
|
||||
solar_system_id: character.solar_system_id,
|
||||
structure_id: character.structure_id,
|
||||
station_id: character.station_id
|
||||
})
|
||||
|
||||
WandererApp.Character.update_character_state(character_id, character_state)
|
||||
end
|
||||
end,
|
||||
@@ -239,7 +258,7 @@ defmodule WandererApp.Character.TrackerManager.Impl do
|
||||
def handle_info(_event, state),
|
||||
do: state
|
||||
|
||||
defp character_is_present(map_id, character_id) do
|
||||
def character_is_present(map_id, character_id) do
|
||||
{:ok, presence_character_ids} =
|
||||
WandererApp.Cache.lookup("map_#{map_id}:presence_character_ids", [])
|
||||
|
||||
|
||||
@@ -177,6 +177,10 @@ defmodule WandererApp.Character.TrackerPool do
|
||||
characters
|
||||
|> Enum.each(fn character_id ->
|
||||
WandererApp.Character.update_character(character_id, %{online: false})
|
||||
|
||||
WandererApp.Character.update_character_state(character_id, %{
|
||||
is_online: false
|
||||
})
|
||||
end)
|
||||
|
||||
{:noreply, state}
|
||||
|
||||
@@ -121,7 +121,6 @@ defmodule WandererApp.Character.TrackingUtils do
|
||||
WandererApp.MapCharacterSettingsRepo.untrack(existing_settings)
|
||||
|
||||
:ok = untrack([character], map_id, caller_pid)
|
||||
:ok = remove_characters([character], map_id)
|
||||
{:ok, updated_settings}
|
||||
else
|
||||
{:ok, existing_settings}
|
||||
@@ -132,7 +131,6 @@ defmodule WandererApp.Character.TrackingUtils do
|
||||
if track do
|
||||
{:ok, updated_settings} = WandererApp.MapCharacterSettingsRepo.track(existing_settings)
|
||||
:ok = track([character], map_id, true, caller_pid)
|
||||
:ok = add_characters([character], map_id, true)
|
||||
{:ok, updated_settings}
|
||||
else
|
||||
{:ok, existing_settings}
|
||||
@@ -149,7 +147,6 @@ defmodule WandererApp.Character.TrackingUtils do
|
||||
})
|
||||
|
||||
:ok = track([character], map_id, true, caller_pid)
|
||||
:ok = add_characters([character], map_id, true)
|
||||
{:ok, settings}
|
||||
else
|
||||
{:error, "Character settings not found"}
|
||||
@@ -231,15 +228,15 @@ defmodule WandererApp.Character.TrackingUtils do
|
||||
with false <- is_nil(caller_pid) do
|
||||
character_ids = characters |> Enum.map(& &1.id)
|
||||
|
||||
characters
|
||||
|> Enum.each(fn character ->
|
||||
WandererAppWeb.Presence.update(caller_pid, map_id, character.id, %{
|
||||
character_ids
|
||||
|> Enum.each(fn character_id ->
|
||||
WandererAppWeb.Presence.update(caller_pid, map_id, character_id, %{
|
||||
tracked: false,
|
||||
from: DateTime.utc_now()
|
||||
})
|
||||
end)
|
||||
|
||||
WandererApp.Map.Server.untrack_characters(map_id, character_ids)
|
||||
# WandererApp.Map.Server.untrack_characters(map_id, character_ids)
|
||||
|
||||
:ok
|
||||
else
|
||||
@@ -249,19 +246,19 @@ defmodule WandererApp.Character.TrackingUtils do
|
||||
end
|
||||
end
|
||||
|
||||
def add_characters([], _map_id, _track_character), do: :ok
|
||||
# def add_characters([], _map_id, _track_character), do: :ok
|
||||
|
||||
def add_characters([character | characters], map_id, track_character) do
|
||||
:ok = WandererApp.Map.Server.add_character(map_id, character, track_character)
|
||||
add_characters(characters, map_id, track_character)
|
||||
end
|
||||
# def add_characters([character | characters], map_id, track_character) do
|
||||
# :ok = WandererApp.Map.Server.add_character(map_id, character, track_character)
|
||||
# add_characters(characters, map_id, track_character)
|
||||
# end
|
||||
|
||||
def remove_characters([], _map_id), do: :ok
|
||||
# def remove_characters([], _map_id), do: :ok
|
||||
|
||||
def remove_characters([character | characters], map_id) do
|
||||
:ok = WandererApp.Map.Server.remove_character(map_id, character.id)
|
||||
remove_characters(characters, map_id)
|
||||
end
|
||||
# def remove_characters([character | characters], map_id) do
|
||||
# :ok = WandererApp.Map.Server.remove_character(map_id, character.id)
|
||||
# remove_characters(characters, map_id)
|
||||
# end
|
||||
|
||||
def get_main_character(
|
||||
nil,
|
||||
|
||||
@@ -102,7 +102,7 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
{:ok, []}
|
||||
end
|
||||
|
||||
chains = _remove_intersection([map_chains | thera_chains] |> List.flatten())
|
||||
chains = remove_intersection([map_chains | thera_chains] |> List.flatten())
|
||||
|
||||
chains =
|
||||
case routes_settings.include_cruise do
|
||||
@@ -302,7 +302,7 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
opts: [ttl: @ttl]
|
||||
)
|
||||
def get_killmail(killmail_id, killmail_hash, opts \\ []) do
|
||||
get("/killmails/#{killmail_id}/#{killmail_hash}/", opts)
|
||||
get("/killmails/#{killmail_id}/#{killmail_hash}/", opts, @cache_opts)
|
||||
end
|
||||
|
||||
@decorate cacheable(
|
||||
@@ -325,7 +325,8 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
def get_character_info(eve_id, opts \\ []) do
|
||||
case get(
|
||||
"/characters/#{eve_id}/",
|
||||
opts
|
||||
opts,
|
||||
@cache_opts
|
||||
) do
|
||||
{:ok, result} -> {:ok, result |> Map.put("eve_id", eve_id)}
|
||||
{:error, error} -> {:error, error}
|
||||
@@ -339,25 +340,35 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
def get_custom_route_base_url, do: WandererApp.Env.custom_route_base_url()
|
||||
|
||||
def get_character_wallet(character_eve_id, opts \\ []),
|
||||
do: _get_character_auth_data(character_eve_id, "wallet", opts)
|
||||
do: get_character_auth_data(character_eve_id, "wallet", opts ++ @cache_opts)
|
||||
|
||||
def get_corporation_wallets(corporation_id, opts \\ []),
|
||||
do: _get_corporation_auth_data(corporation_id, "wallets", opts)
|
||||
do: get_corporation_auth_data(corporation_id, "wallets", opts)
|
||||
|
||||
def get_corporation_wallet_journal(corporation_id, division, opts \\ []),
|
||||
do: _get_corporation_auth_data(corporation_id, "wallets/#{division}/journal", opts)
|
||||
do:
|
||||
get_corporation_auth_data(
|
||||
corporation_id,
|
||||
"wallets/#{division}/journal",
|
||||
opts
|
||||
)
|
||||
|
||||
def get_corporation_wallet_transactions(corporation_id, division, opts \\ []),
|
||||
do: _get_corporation_auth_data(corporation_id, "wallets/#{division}/transactions", opts)
|
||||
do:
|
||||
get_corporation_auth_data(
|
||||
corporation_id,
|
||||
"wallets/#{division}/transactions",
|
||||
opts
|
||||
)
|
||||
|
||||
def get_character_location(character_eve_id, opts \\ []),
|
||||
do: _get_character_auth_data(character_eve_id, "location", opts)
|
||||
do: get_character_auth_data(character_eve_id, "location", opts ++ @cache_opts)
|
||||
|
||||
def get_character_online(character_eve_id, opts \\ []),
|
||||
do: _get_character_auth_data(character_eve_id, "online", opts)
|
||||
do: get_character_auth_data(character_eve_id, "online", opts ++ @cache_opts)
|
||||
|
||||
def get_character_ship(character_eve_id, opts \\ []),
|
||||
do: _get_character_auth_data(character_eve_id, "ship", opts)
|
||||
do: get_character_auth_data(character_eve_id, "ship", opts ++ @cache_opts)
|
||||
|
||||
def search(character_eve_id, opts \\ []) do
|
||||
search_val = to_string(opts[:params][:search] || "")
|
||||
@@ -372,7 +383,7 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
]
|
||||
|
||||
merged_opts = Keyword.put(opts, :params, query_params)
|
||||
_search(character_eve_id, search_val, categories_val, merged_opts)
|
||||
get_search(character_eve_id, search_val, categories_val, merged_opts)
|
||||
end
|
||||
|
||||
@decorate cacheable(
|
||||
@@ -380,11 +391,11 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
key: "search-#{character_eve_id}-#{categories_val}-#{search_val |> Slug.slugify()}",
|
||||
opts: [ttl: @ttl]
|
||||
)
|
||||
defp _search(character_eve_id, search_val, categories_val, merged_opts) do
|
||||
_get_character_auth_data(character_eve_id, "search", merged_opts)
|
||||
defp get_search(character_eve_id, search_val, categories_val, merged_opts) do
|
||||
get_character_auth_data(character_eve_id, "search", merged_opts)
|
||||
end
|
||||
|
||||
defp _remove_intersection(pairs_arr) do
|
||||
defp remove_intersection(pairs_arr) do
|
||||
tuples = pairs_arr |> Enum.map(fn x -> {x.first, x.second} end)
|
||||
|
||||
tuples
|
||||
@@ -405,9 +416,9 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
end
|
||||
|
||||
defp _get_routes(origin, destination, params, opts),
|
||||
do: _get_routes_eve(origin, destination, params, opts)
|
||||
do: get_routes_eve(origin, destination, params, opts)
|
||||
|
||||
defp _get_routes_eve(origin, destination, params, opts) do
|
||||
defp get_routes_eve(origin, destination, params, opts) do
|
||||
esi_params =
|
||||
Map.merge(params, %{
|
||||
connections: params.connections |> Enum.join(","),
|
||||
@@ -416,7 +427,8 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
|
||||
get(
|
||||
"/route/#{origin}/#{destination}/?#{esi_params |> Plug.Conn.Query.encode()}",
|
||||
opts
|
||||
opts,
|
||||
@cache_opts
|
||||
)
|
||||
end
|
||||
|
||||
@@ -426,17 +438,19 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
do:
|
||||
get(
|
||||
"/alliances/#{alliance_eve_id}/#{info_path}",
|
||||
opts
|
||||
opts,
|
||||
@cache_opts
|
||||
)
|
||||
|
||||
defp _get_corporation_info(corporation_eve_id, info_path, opts),
|
||||
do:
|
||||
get(
|
||||
"/corporations/#{corporation_eve_id}/#{info_path}",
|
||||
opts
|
||||
opts,
|
||||
@cache_opts
|
||||
)
|
||||
|
||||
defp _get_character_auth_data(character_eve_id, info_path, opts) do
|
||||
defp get_character_auth_data(character_eve_id, info_path, opts) do
|
||||
path = "/characters/#{character_eve_id}/#{info_path}"
|
||||
|
||||
auth_opts =
|
||||
@@ -445,7 +459,7 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
|
||||
character_id = opts |> Keyword.get(:character_id, nil)
|
||||
|
||||
if not _is_access_token_expired?(character_id) do
|
||||
if not is_access_token_expired?(character_id) do
|
||||
get(
|
||||
path,
|
||||
auth_opts,
|
||||
@@ -456,7 +470,7 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
end
|
||||
end
|
||||
|
||||
defp _is_access_token_expired?(character_id) do
|
||||
defp is_access_token_expired?(character_id) do
|
||||
{:ok, %{expires_at: expires_at} = _character} =
|
||||
WandererApp.Character.get_character(character_id)
|
||||
|
||||
@@ -465,13 +479,13 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
expires_at - now <= 0
|
||||
end
|
||||
|
||||
defp _get_corporation_auth_data(corporation_eve_id, info_path, opts),
|
||||
defp get_corporation_auth_data(corporation_eve_id, info_path, opts),
|
||||
do:
|
||||
get(
|
||||
"/corporations/#{corporation_eve_id}/#{info_path}",
|
||||
[params: opts[:params] || []] ++
|
||||
(opts |> get_auth_opts()),
|
||||
opts
|
||||
opts ++ @cache_opts
|
||||
)
|
||||
|
||||
defp with_user_agent_opts(opts) do
|
||||
@@ -513,7 +527,7 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
|> Keyword.merge(@timeout_opts)
|
||||
) do
|
||||
{:ok, %{status: 200, body: body, headers: headers}} ->
|
||||
maybe_cache_response(path, body, headers)
|
||||
maybe_cache_response(path, body, headers, opts)
|
||||
|
||||
{:ok, body}
|
||||
|
||||
@@ -530,11 +544,9 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
get_retry(path, api_opts, opts, :error_limited)
|
||||
|
||||
{:ok, %{status: status}} ->
|
||||
IO.inspect(status)
|
||||
{:error, "Unexpected status: #{status}"}
|
||||
|
||||
{:error, _reason} ->
|
||||
IO.inspect(_reason)
|
||||
{:error, "Request failed"}
|
||||
end
|
||||
rescue
|
||||
@@ -545,18 +557,20 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_cache_response(path, body, %{"expires" => [expires]})
|
||||
defp maybe_cache_response(path, body, %{"expires" => [expires]}, opts)
|
||||
when is_binary(path) and not is_nil(expires) do
|
||||
try do
|
||||
cached_ttl =
|
||||
DateTime.diff(Timex.parse!(expires, "{RFC1123}"), DateTime.utc_now(), :millisecond)
|
||||
if opts |> Keyword.get(:cache, false) do
|
||||
cached_ttl =
|
||||
DateTime.diff(Timex.parse!(expires, "{RFC1123}"), DateTime.utc_now(), :millisecond)
|
||||
|
||||
Cachex.put(
|
||||
:api_cache,
|
||||
path,
|
||||
body,
|
||||
ttl: cached_ttl
|
||||
)
|
||||
Cachex.put(
|
||||
:api_cache,
|
||||
path,
|
||||
body,
|
||||
ttl: cached_ttl
|
||||
)
|
||||
end
|
||||
rescue
|
||||
e ->
|
||||
@logger.error(Exception.message(e))
|
||||
@@ -565,7 +579,7 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_cache_response(_path, _body, _headers), do: :ok
|
||||
defp maybe_cache_response(_path, _body, _headers, _opts), do: :ok
|
||||
|
||||
defp post(url, opts) do
|
||||
try do
|
||||
@@ -604,7 +618,7 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
if not refresh_token? or is_nil(character_id) or retry_count >= @api_retry_count do
|
||||
{:error, status}
|
||||
else
|
||||
case _refresh_token(character_id) do
|
||||
case refresh_token(character_id) do
|
||||
{:ok, token} ->
|
||||
auth_opts = [access_token: token.access_token] |> get_auth_opts()
|
||||
|
||||
@@ -620,7 +634,7 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
end
|
||||
end
|
||||
|
||||
defp _refresh_token(character_id) do
|
||||
defp refresh_token(character_id) do
|
||||
{:ok, %{expires_at: expires_at, refresh_token: refresh_token, scopes: scopes} = character} =
|
||||
WandererApp.Character.get_character(character_id)
|
||||
|
||||
@@ -675,6 +689,17 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
{:error, :invalid_grant}
|
||||
end
|
||||
|
||||
defp handle_refresh_token_result(
|
||||
{:error, %OAuth2.Error{reason: :econnrefused} = error},
|
||||
character,
|
||||
character_id,
|
||||
expires_at,
|
||||
scopes
|
||||
) do
|
||||
Logger.warning("Failed to refresh token for #{character_id}: #{inspect(error)}")
|
||||
{:error, :econnrefused}
|
||||
end
|
||||
|
||||
defp handle_refresh_token_result(
|
||||
{:error, %OAuth2.Error{} = error},
|
||||
character,
|
||||
@@ -696,8 +721,8 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
defp invalidate_character_tokens(character, character_id, expires_at, scopes) do
|
||||
attrs = %{access_token: nil, refresh_token: nil, expires_at: expires_at, scopes: scopes}
|
||||
|
||||
with {:ok, _} <- WandererApp.Api.Character.update(character, attrs),
|
||||
{:ok, _} <- WandererApp.Character.update_character(character_id, attrs) do
|
||||
with {:ok, _} <- WandererApp.Api.Character.update(character, attrs) do
|
||||
WandererApp.Character.update_character(character_id, attrs)
|
||||
:ok
|
||||
else
|
||||
error ->
|
||||
|
||||
@@ -96,7 +96,7 @@ defmodule WandererApp.Map do
|
||||
map_id
|
||||
|> get_map!()
|
||||
|> Map.get(:characters, [])
|
||||
|> Enum.map(&WandererApp.Character.get_character!(&1))
|
||||
|> Enum.map(fn character_id -> WandererApp.Character.get_map_character!(map_id, character_id) end)
|
||||
|
||||
def list_systems(map_id),
|
||||
do: {:ok, map_id |> get_map!() |> Map.get(:systems, Map.new()) |> Map.values()}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user