Compare commits

...

33 Commits

Author SHA1 Message Date
Dmitry Popov
36f424da0b Merge branch 'main' into update-lifetime 2025-09-26 15:45:19 +02:00
Dmitry Popov
c0a65d5a23 Merge branch 'main' into update-lifetime 2025-09-26 00:57:26 +02:00
Dmitry Popov
02e31333d2 chore: fix 2025-09-26 00:54:55 +02:00
Dmitry Popov
d69616119d feat(Core): Updated connections EOL logic 2025-09-26 00:54:14 +02:00
Dmitry Popov
dbc770d40b Merge branch 'update-lifetime' of github.com:wanderer-industries/wanderer into update-lifetime 2025-09-24 18:44:10 +02:00
CI
e69a8fece5 chore: [skip ci] 2025-09-24 16:38:54 +00:00
CI
cf20be8a77 chore: release version v1.78.1 2025-09-24 16:38:54 +00:00
Dmitry Popov
450bcb649c Merge pull request #521 from wanderer-industries/kills-fix
Kills fix
2025-09-24 20:38:29 +04:00
Dmitry Popov
a00395351e Merge pull request #513 from guarzo/guarzo/killfilter
fix: removed wormhole only logic error
2025-09-24 20:33:00 +04:00
DanSylvest
3b24c760ff fix(Map): Fixed eslint problems 2025-09-24 13:12:32 +03:00
DanSylvest
3801f0be18 Merge branch 'main' into update-lifetime
# Conflicts:
#	assets/js/hooks/Mapper/components/map/components/ContextMenuConnection/ContextMenuConnection.tsx
#	assets/js/hooks/Mapper/components/mapRootContent/components/SignatureSettings/SignatureSettings.tsx
2025-09-24 13:11:02 +03:00
Dmitry Popov
f3104db2e4 Merge branch 'update-lifetime' of github.com:wanderer-industries/wanderer into update-lifetime 2025-09-23 20:19:53 +02:00
CI
602b1028c3 chore: [skip ci] 2025-09-23 16:27:20 +00:00
CI
4f98e979a2 chore: release version v1.78.0 2025-09-23 16:27:20 +00:00
Dmitry Popov
e0f46c4af7 Merge pull request #519 from wanderer-industries/jumpgates
Jumpgates
2025-09-23 20:23:25 +04:00
Dmitry Popov
bc8a9a2b85 Merge branch 'main' into jumpgates 2025-09-23 18:23:11 +02:00
Dmitry Popov
c2b03f925d Merge pull request #518 from leesolway/drag-dialog
Drag signature dialog
2025-09-23 20:22:47 +04:00
Dmitry Popov
efa2e52054 Merge branch 'main' into jumpgates 2025-09-23 18:18:32 +02:00
Dmitry Popov
cedf5761f8 Merge branch 'main' into jumpgates 2025-09-23 17:51:45 +02:00
Dmitry Popov
f601bb8751 Merge branch 'main' into jumpgates 2025-09-23 17:48:59 +02:00
Dmitry Popov
c39d2a56d2 Merge branch 'main' into jumpgates 2025-09-23 17:19:05 +02:00
Lee Solway
805722bbe8 Merge branch 'wanderer-industries:main' into drag-dialog 2025-09-20 16:05:34 +01:00
Lee Solway
fe3e38343b SystemSettingsDialog & SignatureSettings draggable 2025-09-19 17:35:28 +01:00
DanSylvest
616e82c497 fix(Map): Add support for Bridge. Made all tooltips left and right paddings. 2025-09-18 11:52:16 +03:00
guarzo
ab7e47b91f pr feedback 2025-09-18 06:18:23 +00:00
guarzo
cf1c103a46 fix: pr feedback 2025-09-17 15:50:26 +00:00
CI
71202a4a29 chore: [skip ci] 2025-09-14 15:00:45 +00:00
DanSylvest
c789b69b54 fix(Map): Update lifetime design and buttons 2025-09-13 19:17:00 +03:00
Dmitry Popov
24c32511d8 Merge branch 'main' into jumpgates 2025-09-13 11:30:40 +02:00
Dmitry Popov
302fb0642d chore: update connection time values 2025-09-13 11:24:11 +02:00
DanSylvest
33acd55eaa fix(Map): Update wormhole lifetime UI and removed unnecessary code 2025-09-12 11:05:57 +03:00
guarzo
873946a1a6 fix: removed wormhole only logic error 2025-09-05 01:43:47 +00:00
Dmitry Popov
854524a03c feat(Core): added support for jumpgates connection type 2025-03-23 21:51:26 +01:00
54 changed files with 889 additions and 485 deletions

View File

@@ -2,6 +2,30 @@
<!-- changelog -->
## [v1.78.1](https://github.com/wanderer-industries/wanderer/compare/v1.78.0...v1.78.1) (2025-09-24)
### Bug Fixes:
* pr feedback
* removed wormhole only logic error
## [v1.78.0](https://github.com/wanderer-industries/wanderer/compare/v1.77.19...v1.78.0) (2025-09-23)
### Features:
* Core: added support for jumpgates connection type
### Bug Fixes:
* Map: Add support for Bridge. Made all tooltips left and right paddings.
## [v1.77.19](https://github.com/wanderer-industries/wanderer/compare/v1.77.18...v1.77.19) (2025-09-14)

View File

@@ -18,5 +18,28 @@ module.exports = {
'react/react-in-jsx-scope': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
"linebreak-style": "off",
"no-restricted-imports": [
"error",
{
"paths": [
{
"name": "primereact/button",
"importNames": ["Button"],
"message": "Use WdButton instead Button"
}
]
}
],
"react/forbid-elements": [
"error",
{
"forbid": [
{
"element": "Button",
"message": "Use WdButton instead Button"
}
]
}
]
},
};

View File

@@ -5,8 +5,7 @@ import { SolarSystemRawType } from '@/hooks/Mapper/types';
import { getSystemById } from '@/hooks/Mapper/helpers';
import clsx from 'clsx';
import { GRADIENT_MENU_ACTIVE_CLASSES } from '@/hooks/Mapper/constants.ts';
import { LayoutEventBlocker } from '@/hooks/Mapper/components/ui-kit';
import { Button } from 'primereact/button';
import { LayoutEventBlocker, WdButton } from '@/hooks/Mapper/components/ui-kit';
const AVAILABLE_TAGS = [
'A',
@@ -61,7 +60,7 @@ export const useTagMenu = (
<LayoutEventBlocker className="flex flex-col gap-1 w-[200px] h-full px-2">
<div className="grid grid-cols-[auto_auto_auto_auto_auto_auto] gap-1">
{AVAILABLE_TAGS.map(x => (
<Button
<WdButton
outlined={system?.tag !== x}
severity="warning"
key={x}
@@ -71,9 +70,9 @@ export const useTagMenu = (
onClick={() => system?.tag !== x && onSystemTag(x)}
>
{x}
</Button>
</WdButton>
))}
<Button
<WdButton
disabled={!isSelectedTag}
icon="pi pi-ban"
size="small"
@@ -81,7 +80,7 @@ export const useTagMenu = (
outlined
severity="help"
onClick={() => onSystemTag()}
></Button>
></WdButton>
</div>
</LayoutEventBlocker>
);

View File

@@ -8,6 +8,10 @@
background-image: linear-gradient(207deg, transparent, var(--conn-frigate));
}
.ConnectionBridge {
background-image: linear-gradient(207deg, transparent, var(--conn-bridge));
}
.ConnectionSave {
background-image: linear-gradient(207deg, transparent, var(--conn-save));
}
@@ -15,3 +19,14 @@
.SelectedItem {
background-color: var(--selected-item-bg);
}
.FastActions {
:global {
.p-menuitem-content {
background-color: initial !important;
}
.p-menuitem-content:hover {
background-color: initial !important;
}
}
}

View File

@@ -1,10 +1,3 @@
import React, { RefObject, useMemo } from 'react';
import { ContextMenu } from 'primereact/contextmenu';
import { PrimeIcons } from 'primereact/api';
import { MenuItem } from 'primereact/menuitem';
import { ConnectionType, MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
import clsx from 'clsx';
import classes from './ContextMenuConnection.module.scss';
import {
MASS_STATE_NAMES,
MASS_STATE_NAMES_ORDER,
@@ -13,14 +6,25 @@ import {
SHIP_SIZES_NAMES_SHORT,
SHIP_SIZES_SIZE,
} from '@/hooks/Mapper/components/map/constants.ts';
import { ConnectionType, MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
import clsx from 'clsx';
import { PrimeIcons } from 'primereact/api';
import { ContextMenu } from 'primereact/contextmenu';
import { MenuItem } from 'primereact/menuitem';
import React, { RefObject, useMemo } from 'react';
import { Edge } from 'reactflow';
import { LifetimeActionsWrapper } from '@/hooks/Mapper/components/map/components/ContextMenuConnection/LifetimeActionsWrapper.tsx';
import classes from './ContextMenuConnection.module.scss';
import { getSystemStaticInfo } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts';
import { isNullsecSpace } from '@/hooks/Mapper/components/map/helpers/isKnownSpace.ts';
export interface ContextMenuConnectionProps {
contextMenuRef: RefObject<ContextMenu>;
onDeleteConnection(): void;
onChangeTimeState(): void;
onChangeTimeState(lifetime: TimeStatus): void;
onChangeMassState(state: MassState): void;
onChangeShipSizeStatus(state: ShipSizeStatus): void;
onChangeType(type: ConnectionType): void;
onToggleMassSave(isLocked: boolean): void;
onHide(): void;
edge?: Edge<SolarSystemConnection>;
@@ -32,6 +36,7 @@ export const ContextMenuConnection: React.FC<ContextMenuConnectionProps> = ({
onChangeTimeState,
onChangeMassState,
onChangeShipSizeStatus,
onChangeType,
onToggleMassSave,
onHide,
edge,
@@ -41,88 +46,128 @@ export const ContextMenuConnection: React.FC<ContextMenuConnectionProps> = ({
return [];
}
const sourceInfo = getSystemStaticInfo(edge.data?.source);
const targetInfo = getSystemStaticInfo(edge.data?.target);
const bothNullsec =
sourceInfo && targetInfo && isNullsecSpace(sourceInfo.system_class) && isNullsecSpace(targetInfo.system_class);
const isFrigateSize = edge.data?.ship_size_type === ShipSizeStatus.small;
const isWormhole = edge.data?.type !== ConnectionType.gate;
if (edge.data?.type === ConnectionType.bridge) {
return [
{
label: `Set as Wormhole`,
icon: 'pi hero-arrow-uturn-left',
command: () => onChangeType(ConnectionType.wormhole),
},
{
label: 'Disconnect',
icon: PrimeIcons.TRASH,
command: onDeleteConnection,
},
];
}
if (edge.data?.type === ConnectionType.gate) {
return [
{
label: 'Disconnect',
icon: PrimeIcons.TRASH,
command: onDeleteConnection,
},
];
}
return [
...(isWormhole
{
className: clsx(classes.FastActions, '!h-[54px]'),
template: () => {
return <LifetimeActionsWrapper lifetime={edge.data?.time_status} onChangeLifetime={onChangeTimeState} />;
},
},
{
label: `Frigate`,
className: clsx({
[classes.ConnectionFrigate]: isFrigateSize,
}),
icon: PrimeIcons.CLOUD,
command: () =>
onChangeShipSizeStatus(
edge.data?.ship_size_type === ShipSizeStatus.small ? ShipSizeStatus.large : ShipSizeStatus.small,
),
},
{
label: `Save mass`,
className: clsx({
[classes.ConnectionSave]: edge.data?.locked,
}),
icon: PrimeIcons.LOCK,
command: () => onToggleMassSave(!edge.data?.locked),
},
...(!isFrigateSize
? [
{
label: `EOL`,
className: clsx({
[classes.ConnectionTimeEOL]: edge.data?.time_status === TimeStatus.eol,
}),
icon: PrimeIcons.CLOCK,
command: onChangeTimeState,
},
{
label: `Frigate`,
className: clsx({
[classes.ConnectionFrigate]: isFrigateSize,
}),
icon: PrimeIcons.CLOUD,
command: () =>
onChangeShipSizeStatus(
edge.data?.ship_size_type === ShipSizeStatus.small ? ShipSizeStatus.large : ShipSizeStatus.small,
),
},
{
label: `Save mass`,
className: clsx({
[classes.ConnectionSave]: edge.data?.locked,
}),
icon: PrimeIcons.LOCK,
command: () => onToggleMassSave(!edge.data?.locked),
},
...(!isFrigateSize
? [
{
label: `Mass status`,
icon: PrimeIcons.CHART_PIE,
items: MASS_STATE_NAMES_ORDER.map(x => ({
label: MASS_STATE_NAMES[x],
className: clsx({
[classes.SelectedItem]: edge.data?.mass_status === x,
}),
command: () => onChangeMassState(x),
})),
},
]
: []),
{
label: `Ship Size`,
icon: PrimeIcons.CLOUD,
items: SHIP_SIZES_NAMES_ORDER.map(x => ({
label: (
<div className="grid grid-cols-[20px_120px_1fr_40px] gap-2 items-center">
<div className="text-[12px] font-bold text-stone-400">{SHIP_SIZES_NAMES_SHORT[x]}</div>
<div>{SHIP_SIZES_NAMES[x]}</div>
<div></div>
<div className="flex justify-end whitespace-nowrap text-[12px] font-bold text-stone-500">
{SHIP_SIZES_SIZE[x]} t.
</div>
</div>
) as unknown as string, // TODO my lovely kostyl
label: `Mass status`,
icon: PrimeIcons.CHART_PIE,
items: MASS_STATE_NAMES_ORDER.map(x => ({
label: MASS_STATE_NAMES[x],
className: clsx({
[classes.SelectedItem]: edge.data?.ship_size_type === x,
[classes.SelectedItem]: edge.data?.mass_status === x,
}),
command: () => onChangeShipSizeStatus(x),
command: () => onChangeMassState(x),
})),
},
]
: []),
{
label: `Ship Size`,
icon: PrimeIcons.CLOUD,
items: SHIP_SIZES_NAMES_ORDER.map(x => ({
label: (
<div className="grid grid-cols-[20px_120px_1fr_40px] gap-2 items-center">
<div className="text-[12px] font-bold text-stone-400">{SHIP_SIZES_NAMES_SHORT[x]}</div>
<div>{SHIP_SIZES_NAMES[x]}</div>
<div></div>
<div className="flex justify-end whitespace-nowrap text-[12px] font-bold text-stone-500">
{SHIP_SIZES_SIZE[x]} t.
</div>
</div>
) as unknown as string, // TODO my lovely kostyl
className: clsx({
[classes.SelectedItem]: edge.data?.ship_size_type === x,
}),
command: () => onChangeShipSizeStatus(x),
})),
},
...(bothNullsec
? [
{
label: `Set as Bridge`,
icon: 'pi hero-forward',
command: () => onChangeType(ConnectionType.bridge),
},
]
: []),
{
label: 'Disconnect',
icon: PrimeIcons.TRASH,
command: onDeleteConnection,
},
];
}, [edge, onChangeTimeState, onDeleteConnection, onChangeShipSizeStatus, onToggleMassSave, onChangeMassState]);
}, [
edge,
onChangeTimeState,
onDeleteConnection,
onChangeType,
onChangeShipSizeStatus,
onToggleMassSave,
onChangeMassState,
]);
return (
<>
<ContextMenu model={items} ref={contextMenuRef} onHide={onHide} breakpoint="767px" />
<ContextMenu model={items} ref={contextMenuRef} onHide={onHide} breakpoint="767px" className="!w-[250px]" />
</>
);
};

View File

@@ -0,0 +1,12 @@
import { LayoutEventBlocker } from '@/hooks/Mapper/components/ui-kit';
import { WdLifetimeSelector, WdLifetimeSelectorProps } from '@/hooks/Mapper/components/ui-kit/WdLifetimeSelector.tsx';
export const LifetimeActionsWrapper = (props: WdLifetimeSelectorProps) => {
return (
<LayoutEventBlocker className="flex flex-col gap-1 w-[100%] h-full px-2 pt-[4px]">
<div className="text-[12px] text-stone-500 font-semibold">Life time:</div>
<WdLifetimeSelector {...props} />
</LayoutEventBlocker>
);
};

View File

@@ -30,7 +30,7 @@ export const useContextMenuConnectionHandlers = () => {
setEdge(undefined);
};
const onChangeTimeState = () => {
const onChangeTimeState = (lifetime: TimeStatus) => {
if (!edge || !edge.data) {
return;
}
@@ -40,7 +40,7 @@ export const useContextMenuConnectionHandlers = () => {
data: {
source: edge.source,
target: edge.target,
value: edge.data.time_status === TimeStatus.default ? TimeStatus.eol : TimeStatus.default,
value: lifetime,
},
});
setEdge(undefined);

View File

@@ -56,7 +56,8 @@ export const KillsCounter = ({
className={className}
tooltipClassName="!px-0"
size={size}
interactive={true}
interactive
smallPaddings
>
{children}
</WdTooltipWrapper>

View File

@@ -46,7 +46,13 @@ export const LocalCounter = ({ localCounterCharacters, hasUserCharacters, showIc
[classes.Pathfinder]: theme === AvailableThemes.pathfinder,
})}
>
<WdTooltipWrapper content={pilotTooltipContent} position={TooltipPosition.right} offset={0} interactive={true}>
<WdTooltipWrapper
content={pilotTooltipContent}
position={TooltipPosition.right}
offset={0}
interactive={true}
smallPaddings
>
<div className={clsx(classes.hoverTarget)}>
<div
className={clsx(classes.localCounter, {

View File

@@ -5,6 +5,16 @@
stroke: #80a5c5;
stroke-width: 3px;
&.time1 {
stroke: #f11ab2;
stroke-width: 4px;
}
&.time4 {
stroke: #a654e3;
stroke-width: 4px;
}
&.TimeCrit {
stroke: #f11ab2;
stroke-width: 4px;
@@ -29,6 +39,13 @@
&.Gate {
stroke: #9aff40;
}
&.Bridge {
stroke: #9aff40;
stroke-dasharray: 10 5;
stroke-linecap: round;
}
}
.EdgePathFront {

View File

@@ -9,6 +9,7 @@ import { PrimeIcons } from 'primereact/api';
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
import { SHIP_SIZES_DESCRIPTION, SHIP_SIZES_NAMES_SHORT } from '@/hooks/Mapper/components/map/constants.ts';
import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit';
const MAP_TRANSLATES: Record<string, string> = {
[Position.Top]: 'translate(-48%, 0%)',
@@ -42,7 +43,9 @@ export const SHIP_SIZES_COLORS = {
export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }: EdgeProps<SolarSystemConnection>) => {
const sourceNode = useStore(useCallback(store => store.nodeInternals.get(source), [source]));
const targetNode = useStore(useCallback(store => store.nodeInternals.get(target), [target]));
const isWormhole = data?.type !== ConnectionType.gate;
const isWormhole = data?.type === ConnectionType.wormhole;
const isGate = data?.type === ConnectionType.gate;
const isBridge = data?.type === ConnectionType.bridge;
const {
data: { isThickConnections },
@@ -55,9 +58,7 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
const offset = isThickConnections ? MAP_OFFSETS_TICK[targetPos] : MAP_OFFSETS[targetPos];
const method = isWormhole ? getBezierPath : getBezierPath;
const [edgePath, labelX, labelY] = method({
const [edgePath, labelX, labelY] = getBezierPath({
sourceX: sx - offset.x,
sourceY: sy - offset.y,
sourcePosition: sourcePos,
@@ -67,7 +68,7 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
});
return [edgePath, labelX, labelY, sx, sy, tx, ty, sourcePos, targetPos];
}, [isThickConnections, sourceNode, targetNode, isWormhole]);
}, [isThickConnections, sourceNode, targetNode]);
if (!sourceNode || !targetNode || !data) {
return null;
@@ -79,9 +80,11 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
id={`back_${id}`}
className={clsx(classes.EdgePathBack, {
[classes.Tick]: isThickConnections,
[classes.TimeCrit]: isWormhole && data.time_status === TimeStatus.eol,
[classes.time1]: isWormhole && data.time_status === TimeStatus._1h,
[classes.time4]: isWormhole && data.time_status === TimeStatus._4h,
[classes.Hovered]: hovered,
[classes.Gate]: !isWormhole,
[classes.Gate]: isGate,
[classes.Bridge]: isBridge,
})}
d={path}
markerEnd={markerEnd}
@@ -95,7 +98,8 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
[classes.MassVerge]: isWormhole && data.mass_status === MassState.verge,
[classes.MassHalf]: isWormhole && data.mass_status === MassState.half,
[classes.Frigate]: isWormhole && data.ship_size_type === ShipSizeStatus.small,
[classes.Gate]: !isWormhole,
[classes.Gate]: isGate,
[classes.Bridge]: isBridge,
})}
d={path}
markerEnd={markerEnd}
@@ -147,6 +151,19 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
</WdTooltipWrapper>
)}
{isBridge && (
<WdTooltipWrapper
content="Ansiblex Jump Bridge"
position={TooltipPosition.top}
className={clsx(
classes.LinkLabel,
'pointer-events-auto bg-lime-300 rounded opacity-100 cursor-auto text-neutral-900',
)}
>
B
</WdTooltipWrapper>
)}
{isWormhole && data.ship_size_type !== ShipSizeStatus.large && (
<WdTooltipWrapper
content={SHIP_SIZES_DESCRIPTION[data.ship_size_type]}

View File

@@ -58,6 +58,7 @@ export const UnsplashedSignature = ({ signature }: UnsplashedSignatureProps) =>
</InfoDrawer>
</div>
}
smallPaddings
>
<div className={clsx(classes.Box, whClassStyle)}>
<svg width="13" height="8" viewBox="0 0 13 8" xmlns="http://www.w3.org/2000/svg">

View File

@@ -716,11 +716,12 @@ export const STATUS_CLASSES: Record<number, string> = {
[STATUSES.dangerous]: 'eve-system-status-dangerous',
};
export const TYPE_NAMES_ORDER = [ConnectionType.wormhole, ConnectionType.gate];
export const TYPE_NAMES_ORDER = [ConnectionType.wormhole, ConnectionType.gate, ConnectionType.bridge];
export const TYPE_NAMES = {
[ConnectionType.wormhole]: 'Wormhole',
[ConnectionType.gate]: 'Gate',
[ConnectionType.bridge]: 'Jumpgate',
};
export const MASS_STATE_NAMES_ORDER = [MassState.verge, MassState.half, MassState.normal];

View File

@@ -15,3 +15,12 @@ export const isKnownSpace = (wormholeClassID: number) => {
export const isPossibleSpace = (spaces: number[], wormholeClassID: number) => {
return spaces.includes(wormholeClassID);
};
export const isNullsecSpace = (wormholeClassID: number) => {
switch (wormholeClassID) {
case SOLAR_SYSTEM_CLASS_IDS.ns:
return true;
}
return false;
};

View File

@@ -49,87 +49,91 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
const { charactersUpdated, presentCharacters, characterAdded, characterRemoved, characterUpdated } =
useCommandsCharacters();
useImperativeHandle(ref, () => {
return {
command(type, data) {
switch (type) {
case Commands.init:
mapInit(data as CommandInit);
break;
case Commands.addSystems:
setTimeout(() => mapAddSystems(data as CommandAddSystems), 100);
break;
case Commands.updateSystems:
mapUpdateSystems(data as CommandUpdateSystems);
break;
case Commands.removeSystems:
setTimeout(() => removeSystems(data as CommandRemoveSystems), 100);
break;
case Commands.addConnections:
setTimeout(() => addConnections(data as CommandAddConnections), 100);
break;
case Commands.removeConnections:
setTimeout(() => removeConnections(data as CommandRemoveConnections), 100);
break;
case Commands.charactersUpdated:
charactersUpdated(data as CommandCharactersUpdated);
break;
case Commands.characterAdded:
characterAdded(data as CommandCharacterAdded);
break;
case Commands.characterRemoved:
characterRemoved(data as CommandCharacterRemoved);
break;
case Commands.characterUpdated:
characterUpdated(data as CommandCharacterUpdated);
break;
case Commands.presentCharacters:
presentCharacters(data as CommandPresentCharacters);
break;
case Commands.updateConnection:
updateConnection(data as CommandUpdateConnection);
break;
case Commands.mapUpdated:
mapUpdated(data as CommandMapUpdated);
break;
case Commands.killsUpdated:
killsUpdated(data as CommandKillsUpdated);
break;
useImperativeHandle(
ref,
() => {
return {
command(type, data) {
switch (type) {
case Commands.init:
mapInit(data as CommandInit);
break;
case Commands.addSystems:
setTimeout(() => mapAddSystems(data as CommandAddSystems), 100);
break;
case Commands.updateSystems:
mapUpdateSystems(data as CommandUpdateSystems);
break;
case Commands.removeSystems:
setTimeout(() => removeSystems(data as CommandRemoveSystems), 100);
break;
case Commands.addConnections:
setTimeout(() => addConnections(data as CommandAddConnections), 100);
break;
case Commands.removeConnections:
setTimeout(() => removeConnections(data as CommandRemoveConnections), 100);
break;
case Commands.charactersUpdated:
charactersUpdated(data as CommandCharactersUpdated);
break;
case Commands.characterAdded:
characterAdded(data as CommandCharacterAdded);
break;
case Commands.characterRemoved:
characterRemoved(data as CommandCharacterRemoved);
break;
case Commands.characterUpdated:
characterUpdated(data as CommandCharacterUpdated);
break;
case Commands.presentCharacters:
presentCharacters(data as CommandPresentCharacters);
break;
case Commands.updateConnection:
updateConnection(data as CommandUpdateConnection);
break;
case Commands.mapUpdated:
mapUpdated(data as CommandMapUpdated);
break;
case Commands.killsUpdated:
killsUpdated(data as CommandKillsUpdated);
break;
case Commands.centerSystem:
setTimeout(() => {
const systemId = `${data}`;
centerSystem(systemId as CommandSelectSystem);
}, 100);
break;
case Commands.centerSystem:
setTimeout(() => {
const systemId = `${data}`;
centerSystem(systemId as CommandSelectSystem);
}, 100);
break;
case Commands.selectSystem:
selectSystems({ systems: [data as string], delay: 500 });
break;
case Commands.selectSystem:
selectSystems({ systems: [data as string], delay: 500 });
break;
case Commands.selectSystems:
selectSystems(data as CommandSelectSystems);
break;
case Commands.selectSystems:
selectSystems(data as CommandSelectSystems);
break;
case Commands.pingAdded:
case Commands.pingCancelled:
case Commands.routes:
case Commands.signaturesUpdated:
case Commands.linkSignatureToSystem:
case Commands.detailedKillsUpdated:
case Commands.characterActivityData:
case Commands.trackingCharactersData:
case Commands.updateActivity:
case Commands.updateTracking:
case Commands.userSettingsUpdated:
// do nothing
break;
case Commands.pingAdded:
case Commands.pingCancelled:
case Commands.routes:
case Commands.signaturesUpdated:
case Commands.linkSignatureToSystem:
case Commands.detailedKillsUpdated:
case Commands.characterActivityData:
case Commands.trackingCharactersData:
case Commands.updateActivity:
case Commands.updateTracking:
case Commands.userSettingsUpdated:
// do nothing
break;
default:
console.warn(`Map handlers: Unknown command: ${type}`, data);
break;
}
},
};
}, []);
default:
console.warn(`Map handlers: Unknown command: ${type}`, data);
break;
}
},
};
},
[],
);
};

View File

@@ -118,6 +118,7 @@ $homeDark30: color.adjust($homeBase, $lightness: -30%);
--conn-time-eol: #7452c3e3;
--conn-frigate: #325d88;
--conn-bridge: rgba(135, 185, 93, 0.85);
--conn-save: rgba(155, 102, 45, 0.85);
--selected-item-bg: rgba(98, 98, 98, 0.33);
}

View File

@@ -1,11 +1,10 @@
import { Dialog } from 'primereact/dialog';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useCallback, useRef, useState } from 'react';
import { Button } from 'primereact/button';
import { IconField } from 'primereact/iconfield';
import { AutoComplete } from 'primereact/autocomplete';
import { OutCommand, SearchSystemItem } from '@/hooks/Mapper/types';
import { SystemViewStandalone, WHClassView, WHEffectView } from '@/hooks/Mapper/components/ui-kit';
import { SystemViewStandalone, WdButton, WHClassView, WHEffectView } from '@/hooks/Mapper/components/ui-kit';
import classes from './AddSystemDialog.module.scss';
import clsx from 'clsx';
@@ -34,6 +33,7 @@ export const AddSystemDialog = ({
data: { wormholesData },
} = useMapRootState();
// TODO fix it
const inputRef = useRef<any>();
const onShow = useCallback(() => {
inputRef.current?.focus();
@@ -62,6 +62,7 @@ export const AddSystemDialog = ({
},
});
// TODO fix it
let prepared = (result.systems as SearchSystemItem[]).sort((a, b) => {
const amatch = a.label.indexOf(query);
const bmatch = b.label.indexOf(query);
@@ -189,7 +190,7 @@ export const AddSystemDialog = ({
</div>
<div className="flex gap-2 justify-end">
<Button
<WdButton
onClick={handleSubmit}
outlined
disabled={!selectedItem || selectedItem.length !== 1}

View File

@@ -4,6 +4,7 @@ import {
SystemView,
TimeAgo,
TooltipPosition,
WdButton,
WdImgButton,
WdImgButtonTooltip,
} from '@/hooks/Mapper/components/ui-kit';
@@ -13,7 +14,6 @@ import { PingsPlacement } from '@/hooks/Mapper/mapRootProvider/types.ts';
import { Commands, OutCommand, PingType } from '@/hooks/Mapper/types';
import clsx from 'clsx';
import { PrimeIcons } from 'primereact/api';
import { Button } from 'primereact/button';
import { ConfirmPopup } from 'primereact/confirmpopup';
import { Toast } from 'primereact/toast';
import { useCallback, useEffect, useMemo, useRef } from 'react';
@@ -256,7 +256,7 @@ export const PingsInterface = ({ hasLeftOffset }: PingsInterfaceProps) => {
)}
></Toast>
<Button
<WdButton
icon="pi pi-bell"
severity="warning"
aria-label="Notification"

View File

@@ -3,11 +3,10 @@ import { Dialog } from 'primereact/dialog';
import { getSystemById } from '@/hooks/Mapper/helpers';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useCallback, useEffect, useRef, useState } from 'react';
import { Button } from 'primereact/button';
import { OutCommand } from '@/hooks/Mapper/types';
import { IconField } from 'primereact/iconfield';
import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager.ts';
import { WdImageSize, WdImgButton, TooltipPosition } from '@/hooks/Mapper/components/ui-kit';
import { TooltipPosition, WdButton, WdImageSize, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
interface SystemCustomLabelDialog {
systemId: string;
@@ -126,7 +125,7 @@ export const SystemCustomLabelDialog = ({ systemId, visible, setVisible }: Syste
</div>
<div className="flex gap-2 justify-end">
<Button onClick={handleSave} outlined size="small" label="Save"></Button>
<WdButton onClick={handleSave} outlined size="small" label="Save"></WdButton>
</div>
</div>
</form>

View File

@@ -9,10 +9,9 @@ import {
} from '@/hooks/Mapper/components/map/constants.ts';
import { SystemSignaturesContent } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignaturesContent';
import { K162_TYPES_MAP } from '@/hooks/Mapper/constants.ts';
import { getWhSize } from '@/hooks/Mapper/helpers/getWhSize';
import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { CommandLinkSignatureToSystem, SignatureGroup, SystemSignature, TimeStatus } from '@/hooks/Mapper/types';
import { CommandLinkSignatureToSystem, SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
import { SETTINGS_KEYS, SignatureSettingsType } from '@/hooks/Mapper/constants/signatures';
@@ -116,14 +115,14 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat
);
const handleSelect = useCallback(
async (signature: SystemSignature) => {
(signature: SystemSignature) => {
if (!signature) {
return;
}
const { outCommand } = ref.current;
await outCommand({
outCommand({
type: OutCommand.linkSignatureToSystem,
data: {
...data,
@@ -131,32 +130,9 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat
},
});
if (parseSignatureCustomInfo(signature.custom_info).isEOL === true) {
await outCommand({
type: OutCommand.updateConnectionTimeStatus,
data: {
source: data.solar_system_source,
target: data.solar_system_target,
value: TimeStatus.eol,
},
});
}
const whShipSize = getWhSize(wormholes, signature.type);
if (whShipSize !== undefined && whShipSize !== null) {
await outCommand({
type: OutCommand.updateConnectionShipSizeType,
data: {
source: data.solar_system_source,
target: data.solar_system_target,
value: whShipSize,
},
});
}
setVisible(false);
},
[data, setVisible, wormholes],
[data, setVisible],
);
useEffect(() => {

View File

@@ -2,10 +2,9 @@ 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 { SystemView, WdButton } from '@/hooks/Mapper/components/ui-kit';
import clsx from 'clsx';
const PING_TITLES = {
@@ -92,7 +91,7 @@ export const SystemPingDialog = ({ systemId, type, visible, setVisible }: System
</div>
<div className="flex gap-2 justify-end">
<Button onClick={handleSave} size="small" severity="danger" label="Ping!"></Button>
<WdButton onClick={handleSave} size="small" severity="danger" label="Ping!" />
</div>
</div>
</form>

View File

@@ -5,10 +5,9 @@ import { getSystemById } from '@/hooks/Mapper/helpers';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useMapGetOption } from '@/hooks/Mapper/mapRootProvider/hooks/api';
import { useCallback, useEffect, useRef, useState } from 'react';
import { Button } from 'primereact/button';
import { OutCommand } from '@/hooks/Mapper/types';
import { IconField } from 'primereact/iconfield';
import { TooltipPosition, WdImageSize, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
import { TooltipPosition, WdButton, WdImageSize, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager.ts';
import { getSystemStaticInfo } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic';
@@ -114,7 +113,7 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
<Dialog
header="System settings"
visible={visible}
draggable={false}
draggable={true}
style={{ width: '450px' }}
onShow={onShow}
onHide={() => {
@@ -226,7 +225,7 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
</div>
<div className="flex gap-2 justify-end">
<Button onClick={handleSave} outlined size="small" label="Save"></Button>
<WdButton onClick={handleSave} outlined size="small" label="Save" />
</div>
</div>
</form>

View File

@@ -1,11 +1,9 @@
import { Dialog } from 'primereact/dialog';
import { useCallback, useEffect, useRef, useState } from 'react';
import { Button } from 'primereact/button';
import {
RoutesType,
useRouteProvider,
} from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/RoutesProvider.tsx';
import { useRouteProvider } from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/RoutesProvider.tsx';
import { PrettySwitchbox } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/components';
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
import { RoutesType } from '@/hooks/Mapper/mapRootProvider/types.ts';
interface RoutesSettingsDialog {
visible: boolean;
@@ -83,7 +81,7 @@ export const RoutesSettingsDialog = ({ visible, setVisible }: RoutesSettingsDial
</div>
<div className="flex gap-2 justify-end">
<Button onClick={handleSave} outlined size="small" label="Apply"></Button>
<WdButton onClick={handleSave} outlined size="small" label="Apply"></WdButton>
</div>
</div>
</Dialog>

View File

@@ -1,6 +1,5 @@
import { Dialog } from 'primereact/dialog';
import { useCallback, useState } from 'react';
import { Button } from 'primereact/button';
import { TabPanel, TabView } from 'primereact/tabview';
import { PrettySwitchbox } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/components';
import { Dropdown } from 'primereact/dropdown';
@@ -10,6 +9,7 @@ import {
SIGNATURE_SETTINGS,
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
import { SignatureSettingsType } from '@/hooks/Mapper/constants/signatures.ts';
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
interface SystemSignatureSettingsDialogProps {
settings: SignatureSettingsType;
@@ -92,7 +92,7 @@ export const SystemSignatureSettingsDialog = ({
</div>
<div className="flex gap-2 justify-end">
<Button onClick={handleSave} outlined size="small" label="Save"></Button>
<WdButton onClick={handleSave} outlined size="small" label="Save" />
</div>
</div>
</Dialog>

View File

@@ -1,13 +1,13 @@
import React, { useEffect, useState, useCallback } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { Dialog } from 'primereact/dialog';
import { Button } from 'primereact/button';
import { AutoComplete } from 'primereact/autocomplete';
import { Calendar } from 'primereact/calendar';
import clsx from 'clsx';
import { StructureItem, StructureStatus, statusesRequiringTimer, formatToISO } from '../helpers';
import { formatToISO, statusesRequiringTimer, StructureItem, StructureStatus } from '../helpers';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { OutCommand } from '@/hooks/Mapper/types';
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
interface StructuresEditDialogProps {
visible: boolean;
@@ -54,14 +54,13 @@ export const SystemStructuresDialog: React.FC<StructuresEditDialogProps> = ({
// If user typed more text but we have partial match in prevResults
if (newQuery.startsWith(prevQuery) && prevResults.length > 0) {
const filtered = prevResults.filter(item =>
item.label.toLowerCase().includes(newQuery.toLowerCase()),
);
const filtered = prevResults.filter(item => item.label.toLowerCase().includes(newQuery.toLowerCase()));
setOwnerSuggestions(filtered);
return;
}
try {
// TODO fix it
const { results = [] } = await outCommand({
type: OutCommand.getCorporationNames,
data: { search: newQuery },
@@ -96,9 +95,7 @@ export const SystemStructuresDialog: React.FC<StructuresEditDialogProps> = ({
// when user picks a corp from auto-complete
const handleSelectOwner = (selected: { label: string; value: string }) => {
setOwnerInput(selected.label);
setEditData(prev =>
prev ? { ...prev, ownerName: selected.label, ownerId: selected.value } : null,
);
setEditData(prev => (prev ? { ...prev, ownerName: selected.label, ownerId: selected.value } : null));
};
const handleStatusChange = (val: string) => {
@@ -125,6 +122,7 @@ export const SystemStructuresDialog: React.FC<StructuresEditDialogProps> = ({
// fetch corporation ticker if we have an ownerId
if (editData.ownerId) {
try {
// TODO fix it
const { ticker } = await outCommand({
type: OutCommand.getCorporationTicker,
data: { corp_id: editData.ownerId },
@@ -157,11 +155,7 @@ export const SystemStructuresDialog: React.FC<StructuresEditDialogProps> = ({
<div className="flex flex-col gap-2 text-[14px]">
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center">
<span>Type:</span>
<input
readOnly
className="p-inputtext p-component cursor-not-allowed"
value={editData.structureType ?? ''}
/>
<input readOnly className="p-inputtext p-component cursor-not-allowed" value={editData.structureType ?? ''} />
</label>
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center">
<span>Name:</span>
@@ -204,10 +198,12 @@ export const SystemStructuresDialog: React.FC<StructuresEditDialogProps> = ({
{statusesRequiringTimer.includes(editData.status) && (
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center">
<span>Timer <br /> (Eve Time):</span>
<span>
Timer <br /> (Eve Time):
</span>
<Calendar
value={editData.endTime ? new Date(editData.endTime) : undefined}
onChange={(e) => handleChange('endTime', e.value ?? '')}
onChange={e => handleChange('endTime', e.value ?? '')}
showTime
hourFormat="24"
dateFormat="yy-mm-dd"
@@ -227,8 +223,8 @@ export const SystemStructuresDialog: React.FC<StructuresEditDialogProps> = ({
</div>
<div className="flex justify-end items-center gap-2 mt-4">
<Button label="Delete" severity="danger" className="p-button-sm" onClick={handleDeleteClick} />
<Button label="Save" className="p-button-sm" onClick={handleSaveClick} />
<WdButton label="Delete" severity="danger" className="p-button-sm" onClick={handleDeleteClick} />
<WdButton label="Save" className="p-button-sm" onClick={handleSaveClick} />
</div>
</Dialog>
);

View File

@@ -16,8 +16,21 @@ const SystemKillsContent = () => {
} = useMapRootState();
const [systemId] = selectedSystems || [];
const whCacheRef = useMemo(() => new Map<number, boolean>(), []);
const systemStaticInfo = getSystemStaticInfo(systemId)!;
const isWormholeSystem = useCallback(
(systemId: number): boolean => {
const cached = whCacheRef.get(systemId);
if (cached !== undefined) return cached;
const info = getSystemStaticInfo(systemId);
const isWH = info?.system_class != null ? isWormholeSpace(Number(info.system_class)) : false;
whCacheRef.set(systemId, isWH);
return isWH;
},
[whCacheRef],
);
const { kills, isLoading, error } = useSystemKills({
systemId,
@@ -30,15 +43,9 @@ const SystemKillsContent = () => {
const showLoading = isLoading && kills.length === 0;
const filteredKills = useMemo(() => {
if (!settingsKills.whOnly || !settingsKills.showAll) return kills;
return kills.filter(kill => {
if (!systemStaticInfo) {
console.warn(`System with id ${kill.solar_system_id} not found.`);
return false;
}
return isWormholeSpace(systemStaticInfo.system_class);
});
}, [kills, settingsKills.whOnly, systemStaticInfo, settingsKills.showAll]);
if (!settingsKills.whOnly) return kills;
return kills.filter(kill => isWormholeSystem(Number(kill.solar_system_id)));
}, [kills, settingsKills.whOnly, isWormholeSystem]);
if (!isSubscriptionActive) {
return (

View File

@@ -1,13 +1,11 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { Dialog } from 'primereact/dialog';
import { Button } from 'primereact/button';
import { WdImgButton } from '@/hooks/Mapper/components/ui-kit';
import { SystemView, TooltipPosition, WdButton, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
import { PrimeIcons } from 'primereact/api';
import {
AddSystemDialog,
SearchOnSubmitCallback,
} from '@/hooks/Mapper/components/mapInterface/components/AddSystemDialog';
import { SystemView, TooltipPosition } from '@/hooks/Mapper/components/ui-kit';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
interface KillsSettingsDialogProps {
@@ -158,7 +156,7 @@ export const KillsSettingsDialog: React.FC<KillsSettingsDialogProps> = ({ visibl
</div>
<div className="flex gap-2 justify-end mt-4">
<Button onClick={handleApply} label="Apply" outlined size="small" />
<WdButton onClick={handleApply} label="Apply" outlined size="small" />
</div>
</div>

View File

@@ -1,22 +1,21 @@
import classes from './Connections.module.scss';
import { Sidebar } from 'primereact/sidebar';
import { useEffect, useMemo, useState, useCallback } from 'react';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { VirtualScroller, VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
import clsx from 'clsx';
import {
ConnectionType,
ConnectionOutput,
ConnectionInfoOutput,
ConnectionOutput,
ConnectionType,
OutCommand,
Passage,
SolarSystemConnection,
} from '@/hooks/Mapper/types';
import clsx from 'clsx';
import { Sidebar } from 'primereact/sidebar';
import { VirtualScroller, VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
import { useCallback, useEffect, useMemo, useState } from 'react';
import classes from './Connections.module.scss';
import { PassageCard } from './PassageCard';
import { InfoDrawer, SystemView } from '@/hooks/Mapper/components/ui-kit';
import { InfoDrawer, SystemView, TimeAgo } from '@/hooks/Mapper/components/ui-kit';
import { kgToTons } from '@/hooks/Mapper/utils/kgToTons.ts';
import { TimeAgo } from '@/hooks/Mapper/components/ui-kit';
import { PassageCard } from './PassageCard';
const sortByDate = (a: string, b: string) => new Date(a).getTime() - new Date(b).getTime();
@@ -78,7 +77,7 @@ export const Connections = ({ selectedConnection, onHide }: OnTheMapProps) => {
}, [connections, selectedConnection]);
const isWormhole = useMemo(() => {
return cnInfo?.type !== ConnectionType.gate;
return cnInfo?.type === ConnectionType.wormhole;
}, [cnInfo]);
const [passages, setPassages] = useState<Passage[]>([]);

View File

@@ -1,7 +1,6 @@
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Toast } from 'primereact/toast';
import { Button } from 'primereact/button';
import { callToastError, callToastSuccess, callToastWarn } from '@/hooks/Mapper/helpers';
import { OutCommand } from '@/hooks/Mapper/types';
import { ConfirmPopup } from 'primereact/confirmpopup';
@@ -10,6 +9,7 @@ import { MapUserSettings, RemoteAdminSettingsResponse } from '@/hooks/Mapper/map
import { parseMapUserSettings } from '@/hooks/Mapper/components/helpers';
import fastDeepEqual from 'fast-deep-equal';
import { useDetectSettingsChanged } from '@/hooks/Mapper/components/hooks';
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
export const AdminSettings = () => {
const {
@@ -92,7 +92,7 @@ export const AdminSettings = () => {
<div className="w-full h-full flex flex-col gap-5">
<div className="flex flex-col gap-1">
<div>
<Button
<WdButton
// @ts-ignore
ref={cfRef}
onClick={cfShow}

View File

@@ -7,8 +7,7 @@ import {
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';
import { Button } from 'primereact/button';
import { TooltipPosition, WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit';
import { TooltipPosition, WdButton, WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit';
import { ConfirmPopup } from 'primereact/confirmpopup';
import { useConfirmPopup } from '@/hooks/Mapper/hooks';
@@ -42,7 +41,7 @@ export const CommonSettings = () => {
<div className="grid grid-cols-[1fr_auto]">
<div />
<WdTooltipWrapper content="This dangerous action. And can not be undone" position={TooltipPosition.top}>
<Button
<WdButton
// @ts-ignore
ref={cfRef}
className="py-[4px]"

View File

@@ -1,10 +1,10 @@
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useCallback, useRef, useState } from 'react';
import { Toast } from 'primereact/toast';
import { Button } from 'primereact/button';
import { OutCommand } from '@/hooks/Mapper/types';
import { Divider } from 'primereact/divider';
import { callToastError, callToastSuccess, callToastWarn } from '@/hooks/Mapper/helpers';
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
type SaveDefaultSettingsReturn = { success: boolean; error: string };
@@ -65,7 +65,7 @@ export const DefaultSettings = () => {
<div className="flex flex-col gap-1">
<div>
<Button
<WdButton
onClick={handleSaveAsDefault}
icon="pi pi-save"
size="small"

View File

@@ -2,13 +2,13 @@ import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useCallback, useEffect, useRef, useState } from 'react';
import { Toast } from 'primereact/toast';
import { parseMapUserSettings } from '@/hooks/Mapper/components/helpers';
import { Button } from 'primereact/button';
import { OutCommand } from '@/hooks/Mapper/types';
import { createDefaultWidgetSettings } from '@/hooks/Mapper/mapRootProvider/helpers/createDefaultWidgetSettings.ts';
import { callToastSuccess } from '@/hooks/Mapper/helpers';
import { ConfirmPopup } from 'primereact/confirmpopup';
import { useConfirmPopup } from '@/hooks/Mapper/hooks';
import { RemoteAdminSettingsResponse } from '@/hooks/Mapper/mapRootProvider/types.ts';
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
export const ServerSettings = () => {
const {
@@ -64,7 +64,7 @@ export const ServerSettings = () => {
<div className="w-full h-full flex flex-col gap-5">
<div className="flex flex-col gap-1">
<div>
<Button
<WdButton
// @ts-ignore
ref={cfRef}
onClick={cfShow}

View File

@@ -2,8 +2,7 @@ import { PrettySwitchbox } from '@/hooks/Mapper/components/mapRootContent/compon
import { WIDGETS_CHECKBOXES_PROPS, WidgetsIds } from '@/hooks/Mapper/components/mapInterface/constants.tsx';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useCallback } from 'react';
import { Button } from 'primereact/button';
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
export interface WidgetsSettingsProps {}
@@ -33,7 +32,7 @@ export const WidgetsSettings = ({}: WidgetsSettingsProps) => {
<div className="grid grid-cols-[1fr_auto]">
<div />
<Button className="py-[4px]" onClick={resetWidgets} outlined size="small" label="Reset Widgets"></Button>
<WdButton className="py-[4px]" onClick={resetWidgets} outlined size="small" label="Reset Widgets" />
</div>
</div>
);

View File

@@ -11,11 +11,11 @@ import {
} from '@/hooks/Mapper/mapRootProvider/constants.ts';
import { MapUserSettings } from '@/hooks/Mapper/mapRootProvider/types.ts';
import { saveTextFile } from '@/hooks/Mapper/utils';
import { Button } from 'primereact/button';
import { ConfirmPopup } from 'primereact/confirmpopup';
import { Dialog } from 'primereact/dialog';
import { Toast } from 'primereact/toast';
import { useCallback, useRef } from 'react';
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
const createSettings = function <T>(lsSettings: string | null, defaultValues: T) {
return {
@@ -139,7 +139,7 @@ export const OldSettingsDialog = () => {
className="w-[640px] h-[400px] text-text-color min-h-0"
footer={
<div className="flex items-center justify-end">
<Button
<WdButton
// @ts-ignore
ref={cfRef}
onClick={cfShow}
@@ -168,7 +168,7 @@ export const OldSettingsDialog = () => {
<div className="h-[30px]"></div>
<div className="flex items-center gap-3">
<Button
<WdButton
onClick={handleExportClipboard}
icon="pi pi-copy"
size="small"
@@ -176,7 +176,7 @@ export const OldSettingsDialog = () => {
label="Export to Clipboard"
/>
<Button
<WdButton
onClick={handleExportAsFile}
icon="pi pi-download"
size="small"

View File

@@ -8,11 +8,14 @@ import {
} from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components';
import { InputText } from 'primereact/inputtext';
import { SystemsSettingsProvider } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/Provider.tsx';
import { Button } from 'primereact/button';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { getWhSize } from '@/hooks/Mapper/helpers/getWhSize';
import { WdButton } from '@/hooks/Mapper/components/ui-kit';
type SystemSignaturePrepared = Omit<SystemSignature, 'linked_system'> & { linked_system: string };
type SystemSignaturePrepared = Omit<SystemSignature, 'linked_system'> & {
linked_system: string;
k162Type: string;
time_status: TimeStatus;
};
export interface MapSettingsProps {
systemId: string;
@@ -22,10 +25,7 @@ export interface MapSettingsProps {
}
export const SignatureSettings = ({ systemId, show, onHide, signatureData }: MapSettingsProps) => {
const {
outCommand,
data: { wormholes },
} = useMapRootState();
const { outCommand } = useMapRootState();
const handleShow = async () => {};
const signatureForm = useForm<Partial<SystemSignaturePrepared>>({});
@@ -52,41 +52,13 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
solar_system_target: values.linked_system,
},
});
// TODO: need fix
if (values.isEOL) {
await outCommand({
type: OutCommand.updateConnectionTimeStatus,
data: {
source: systemId,
target: values.linked_system,
value: TimeStatus.eol,
},
});
}
if (values.type) {
const whShipSize = getWhSize(wormholes, values.type);
if (whShipSize !== undefined && whShipSize !== null) {
await outCommand({
type: OutCommand.updateConnectionShipSizeType,
data: {
source: systemId,
target: values.linked_system,
value: whShipSize,
},
});
}
}
}
out = {
...out,
custom_info: JSON.stringify({
// TODO: need fix
k162Type: values.k162Type,
// TODO: need fix
isEOL: values.isEOL,
time_status: values.time_status,
}),
};
@@ -153,7 +125,7 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
signatureForm.reset();
onHide();
},
[signatureData, signatureForm, outCommand, systemId, onHide, wormholes],
[signatureData, signatureForm, outCommand, systemId, onHide],
);
useEffect(() => {
@@ -165,18 +137,17 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
const { linked_system, custom_info, ...rest } = signatureData;
let k162Type = null;
let isEOL = false;
let time_status = TimeStatus._24h;
if (custom_info) {
const customInfo = JSON.parse(custom_info);
k162Type = customInfo.k162Type;
isEOL = customInfo.isEOL;
time_status = customInfo.time_status;
}
signatureForm.reset({
linked_system: linked_system?.solar_system_id.toString() ?? undefined,
// TODO: need fix
k162Type: k162Type,
isEOL: isEOL,
time_status: time_status,
...rest,
});
}, [signatureForm, signatureData]);
@@ -185,7 +156,8 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
<Dialog
header={`Signature Edit [${signatureData?.eve_id}]`}
visible={show}
draggable={false}
draggable
resizable={false}
style={{ width: '390px' }}
onShow={handleShow}
onHide={() => {
@@ -220,8 +192,8 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
</label>
</div>
<div className="flex gap-2 justify-end">
<Button onClick={handleSave} outlined size="small" label="Save"></Button>
<div className="flex gap-2 justify-end px-[0.75rem] pb-[0.5rem]">
<WdButton type="submit" outlined size="small" label="Save" />
</div>
</div>
</form>

View File

@@ -1,24 +0,0 @@
import { InputSwitch } from 'primereact/inputswitch';
import { Controller, useFormContext } from 'react-hook-form';
import { SystemSignature } from '@/hooks/Mapper/types';
export interface SignatureEOLCheckboxProps {
name: string;
defaultValue?: boolean;
}
export const SignatureEOLCheckbox = ({ name, defaultValue = false }: SignatureEOLCheckboxProps) => {
const { control } = useFormContext<SystemSignature>();
return (
<Controller
// @ts-ignore
name={name}
control={control}
defaultValue={defaultValue}
render={({ field }) => {
return <InputSwitch className="my-1" checked={!!field.value} onChange={e => field.onChange(e.value)} />;
}}
/>
);
};

View File

@@ -3,7 +3,7 @@ import { SystemSignature } from '@/hooks/Mapper/types';
import { SignatureWormholeTypeSelect } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureWormholeTypeSelect';
import { SignatureK162TypeSelect } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureK162TypeSelect';
import { SignatureLeadsToSelect } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureLeadsToSelect';
import { SignatureEOLCheckbox } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureEOLCheckbox';
import { SignatureLifetimeSelect } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureLifetimeSelect.tsx';
import { SignatureTempName } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureTempName.tsx';
export const SignatureGroupContentWormholes = () => {
@@ -29,10 +29,10 @@ export const SignatureGroupContentWormholes = () => {
<SignatureLeadsToSelect name="linked_system" />
</label>
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
<span>EOL:</span>
<SignatureEOLCheckbox name="isEOL" />
</label>
<div className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
<span>Lifetime:</span>
<SignatureLifetimeSelect name="time_status" />
</div>
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
<span>Temp. Name:</span>

View File

@@ -0,0 +1,27 @@
import { Controller, useFormContext } from 'react-hook-form';
import { SystemSignature } from '@/hooks/Mapper/types';
import { WdLifetimeSelector } from '@/hooks/Mapper/components/ui-kit/WdLifetimeSelector.tsx';
export interface SignatureEOLCheckboxProps {
name: string;
defaultValue?: boolean;
}
export const SignatureLifetimeSelect = ({ name, defaultValue = false }: SignatureEOLCheckboxProps) => {
const { control } = useFormContext<SystemSignature>();
return (
<div className="my-1">
<Controller
// @ts-ignore
name={name}
control={control}
defaultValue={defaultValue}
render={({ field }) => {
// @ts-ignore
return <WdLifetimeSelector lifetime={field.value} onChangeLifetime={e => field.onChange(e)} />;
}}
/>
</div>
);
};

View File

@@ -1,3 +1,4 @@
export * from './SignatureGroupSelect';
export * from './SignatureGroupContent';
export * from './SignatureK162TypeSelect';
export * from './SignatureLifetimeSelect';

View File

@@ -0,0 +1,7 @@
// eslint-disable-next-line no-restricted-imports
import { Button, ButtonProps } from 'primereact/button';
export const WdButton = ({ type = 'button', ...props }: ButtonProps) => {
// eslint-disable-next-line react/forbid-elements
return <Button {...props} type={type} />;
};

View File

@@ -0,0 +1,86 @@
import { WdButton } from '@/hooks/Mapper/components/ui-kit/WdButton.tsx';
import { TimeStatus } from '@/hooks/Mapper/types';
import clsx from 'clsx';
import { BUILT_IN_TOOLTIP_OPTIONS } from './constants.ts';
const LIFE_TIME = [
{
id: TimeStatus._1h,
label: '1H',
className: 'bg-purple-400 hover:!bg-purple-400',
inactiveClassName: 'bg-purple-400/30',
description: 'Less than one 1 hours remaining',
},
{
id: TimeStatus._4h,
label: '4H',
className: 'bg-purple-300 hover:!bg-purple-300',
inactiveClassName: 'bg-purple-300/30',
description: 'Less than one 4 hours remaining',
},
{
id: TimeStatus._4h30m,
label: '4.5H',
className: 'bg-indigo-300 hover:!bg-indigo-300',
inactiveClassName: 'bg-indigo-300/30',
description: 'Less than one 4.5 hours remaining. All small holes have such lifetime.',
},
{
id: TimeStatus._16h,
label: '16H',
className: 'bg-orange-300 hover:!bg-orange-300',
inactiveClassName: 'bg-orange-400/30',
description: 'Less than one 16 hours remaining',
},
{
id: TimeStatus._24h,
label: '24H',
className: 'bg-orange-300 hover:!bg-orange-300',
inactiveClassName: 'bg-orange-400/30',
description: 'Less than one 24 hours remaining',
},
{
id: TimeStatus._48h,
label: '48H',
className: 'bg-orange-300 hover:!bg-orange-300',
inactiveClassName: 'bg-orange-400/30',
description: 'Less than one 24 hours remaining. Related only with C6. B041, B520, U319, C391.',
},
];
export interface WdLifetimeSelectorProps {
lifetime?: TimeStatus;
onChangeLifetime(lifetime: TimeStatus): void;
className?: string;
}
export const WdLifetimeSelector = ({
lifetime = TimeStatus._24h,
onChangeLifetime,
className,
}: WdLifetimeSelectorProps) => {
return (
<form>
<div className={clsx('grid grid-cols-[1fr_1fr_1fr_1fr_1fr_1fr] gap-1', className)}>
{LIFE_TIME.map(x => (
<WdButton
key={x.id}
outlined={false}
value={x.label}
tooltip={x.description}
tooltipOptions={BUILT_IN_TOOLTIP_OPTIONS}
size="small"
className={clsx(
`py-[1px] justify-center min-w-auto w-auto border-0 text-[12px] font-bold leading-[20px]`,
{ [x.inactiveClassName]: lifetime !== x.id },
x.className,
)}
onClick={() => onChangeLifetime(x.id)}
>
{x.label}
</WdButton>
))}
</div>
</form>
);
};

View File

@@ -18,6 +18,7 @@ export interface TooltipProps extends Omit<React.HTMLAttributes<HTMLDivElement>,
content: (() => React.ReactNode) | React.ReactNode;
targetSelector?: string;
interactive?: boolean;
smallPaddings?: boolean;
}
export interface OffsetPosition {
@@ -47,6 +48,7 @@ export const WdTooltip = forwardRef(
position: tPosition = TooltipPosition.default,
offset = 5,
interactive = false,
smallPaddings = false,
className,
...restProps
}: TooltipProps,
@@ -264,10 +266,14 @@ export const WdTooltip = forwardRef(
ref={tooltipRef}
className={clsx(
classes.tooltip,
interactive ? 'pointer-events-auto' : 'pointer-events-none',
'absolute px-1 py-1',
'absolute px-2 py-1',
'border rounded-sm border-green-300 border-opacity-10 bg-stone-900 bg-opacity-90',
pos == null && 'invisible',
{
'pointer-events-auto': interactive,
'pointer-events-none': !interactive,
invisible: pos == null,
'!px-1': smallPaddings,
},
className,
)}
style={{

View File

@@ -8,13 +8,26 @@ export type WdTooltipWrapperProps = {
content?: (() => ReactNode) | ReactNode;
size?: TooltipSize;
interactive?: boolean;
smallPaddings?: boolean;
tooltipClassName?: string;
} & Omit<HTMLProps<HTMLDivElement>, 'content' | 'size'> &
Omit<TooltipProps, 'content'>;
export const WdTooltipWrapper = forwardRef<WdTooltipHandlers, WdTooltipWrapperProps>(
(
{ className, children, content, offset, position, targetSelector, interactive, size, tooltipClassName, ...props },
{
className,
children,
content,
offset,
position,
targetSelector,
interactive,
smallPaddings,
size,
tooltipClassName,
...props
},
forwardedRef,
) => {
const suffix = useMemo(() => Math.random().toString(36).slice(2, 7), []);
@@ -31,6 +44,7 @@ export const WdTooltipWrapper = forwardRef<WdTooltipHandlers, WdTooltipWrapperPr
position={position}
content={content}
interactive={interactive}
smallPaddings={smallPaddings}
targetSelector={finalTargetSelector}
className={clsx(size && sizeClass(size), tooltipClassName)}
/>

View File

@@ -0,0 +1,6 @@
export const BUILT_IN_TOOLTIP_OPTIONS = {
mouseTrack: true,
mouseTrackLeft: 10,
className:
'rounded-[3px] bg-stone-900/90 px-1 py-1 [&_.p-tooltip-text]:!text-stone-300 text-[13px] [&_.p-tooltip-text]:!p-1',
};

View File

@@ -21,3 +21,5 @@ export * from './LoadingWrapper';
export * from './WdMenuItem';
export * from './MenuItemWithInfo';
export * from './MarkdownTextViewer.tsx';
export * from './WdButton.tsx';
export * from './constants.ts';

View File

@@ -1,6 +1,7 @@
export enum ConnectionType {
wormhole,
gate,
bridge,
}
export enum MassState {
@@ -10,8 +11,13 @@ export enum MassState {
}
export enum TimeStatus {
default,
eol,
reserved, // TODO: this reserved for not broke prev solution
_1h,
_4h,
_4h30m,
_16h,
_24h,
_48h,
}
export enum ShipSizeStatus {

View File

@@ -147,8 +147,13 @@ defmodule WandererApp.Api.MapConnection do
allow_nil?(true)
end
# where 0 - normal
# where 1 - end of life
# 0 - normal (env settings)
# 1 - EOL 1h
# 2 - EOL 4h
# 3 - EOL 4.5h
# 4 - EOL 16h
# 5 - EOL 24h
# 6 - EOL 48h
attribute :time_status, :integer do
default(0)
@@ -168,6 +173,7 @@ defmodule WandererApp.Api.MapConnection do
# where 0 - Wormhole
# where 1 - Gate
# where 2 - Bridge
attribute :type, :integer do
default(0)

View File

@@ -68,10 +68,38 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
# this class of systems will guaranty that no one real class will take that place
# @unknown 100_100
#
# default (env) setting, not EOL
@connection_time_status_default 0
# EOL 1h
@connection_time_status_eol 1
# EOL 4h
@connection_time_status_eol_4 2
# EOL 4.5h
@connection_time_status_eol_4_5 3
# EOL 16h
@connection_time_status_eol_16 4
# EOL 24h
@connection_time_status_eol_24 5
# EOL 48h
@connection_time_status_eol_48 6
# EOL 1h
@connection_eol_minutes 60
# EOL 4h
@connection_eol_4_minutes 4 * 60
# EOL 4.5h
@connection_eol_4_5_minutes 4.5 * 60
# EOL 16h
@connection_eol_16_minutes 16 * 60
# EOL 24h
@connection_eol_24_minutes 24 * 60
# EOL 48h
@connection_eol_48_minutes 48 * 60
@connection_type_wormhole 0
@connection_type_stargate 1
@connection_type_bridge 2
@medium_ship_size 1
def get_connection_auto_expire_hours(), do: WandererApp.Env.map_connection_auto_expire_hours()
@@ -146,6 +174,18 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
state
end
def update_connection_type(
%{map_id: map_id} = state,
%{
solar_system_source_id: solar_system_source_id,
solar_system_target_id: solar_system_target_id,
character_id: character_id
} = _connection_info,
type
) do
state
end
def get_connection_info(
%{map_id: map_id} = _state,
%{
@@ -182,6 +222,8 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
"map_#{map_id}:conn_#{connection_id}:mark_eol_time",
DateTime.utc_now()
)
set_start_time(map_id, connection_id, DateTime.utc_now())
end
_ ->
@@ -230,35 +272,38 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
state =
map_id
|> WandererApp.Map.list_connections!()
|> Enum.filter(fn %{
id: connection_id,
inserted_at: inserted_at,
solar_system_source: solar_system_source_id,
solar_system_target: solar_system_target_id,
type: type
} ->
connection_start_time = get_start_time(map_id, connection_id)
type != @connection_type_stargate &&
DateTime.diff(DateTime.utc_now(), connection_start_time, :hour) >=
connection_auto_eol_hours &&
is_connection_valid(
:wormholes,
solar_system_source_id,
solar_system_target_id
)
end)
|> Enum.reduce(state, fn %{
id: connection_id,
solar_system_source: solar_system_source_id,
solar_system_target: solar_system_target_id
solar_system_target: solar_system_target_id,
time_status: time_status,
type: type
},
state ->
state
|> update_connection_time_status(%{
solar_system_source_id: solar_system_source_id,
solar_system_target_id: solar_system_target_id,
time_status: @connection_time_status_eol
})
if type == @connection_type_wormhole do
connection_start_time = get_start_time(map_id, connection_id)
new_time_status = get_new_time_status(connection_start_time, time_status)
if new_time_status != time_status &&
is_connection_valid(
:wormholes,
solar_system_source_id,
solar_system_target_id
) do
set_start_time(map_id, connection_id, DateTime.utc_now())
state
|> update_connection_time_status(%{
solar_system_source_id: solar_system_source_id,
solar_system_target_id: solar_system_target_id,
time_status: new_time_status
})
else
state
end
else
state
end
end)
state =
@@ -268,37 +313,38 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
id: connection_id,
solar_system_source: solar_system_source_id,
solar_system_target: solar_system_target_id,
time_status: time_status,
type: type
} ->
connection_mark_eol_time =
get_connection_mark_eol_time(map_id, connection_id)
reverse_connection =
WandererApp.Map.get_connection(
map_id,
solar_system_target_id,
solar_system_source_id
)
is_connection_exist =
is_connection_exist(
map_id,
solar_system_source_id,
solar_system_target_id
) || not is_nil(reverse_connection)
is_connection_valid =
is_connection_valid(
:wormholes,
solar_system_source_id,
solar_system_target_id
)
) ||
not is_nil(
WandererApp.Map.get_connection(
map_id,
solar_system_target_id,
solar_system_source_id
)
)
not is_connection_exist ||
(type != @connection_type_stargate && is_connection_valid &&
DateTime.diff(DateTime.utc_now(), connection_mark_eol_time, :hour) >=
(type == @connection_type_wormhole &&
time_status == @connection_time_status_eol &&
is_connection_valid(
:wormholes,
solar_system_source_id,
solar_system_target_id
) &&
DateTime.diff(
DateTime.utc_now(),
get_connection_mark_eol_time(map_id, connection_id),
:hour
) >=
connection_auto_expire_hours - connection_auto_eol_hours +
+connection_eol_expire_timeout_hours)
connection_eol_expire_timeout_hours)
end)
|> Enum.reduce(state, fn %{
solar_system_source: solar_system_source_id,
@@ -318,28 +364,6 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
when not is_nil(location) and not is_nil(old_location) and
not is_nil(old_location.solar_system_id) and
location.solar_system_id != old_location.solar_system_id do
if not is_manual do
character_id
|> WandererApp.Character.get_character!()
|> case do
nil ->
:ok
character ->
:telemetry.execute([:wanderer_app, :map, :character, :jump], %{count: 1}, %{})
{:ok, _} =
WandererApp.Api.MapChainPassages.new(%{
map_id: map_id,
character_id: character_id,
ship_type_id: character.ship,
ship_name: character.ship_name,
solar_system_source_id: old_location.solar_system_id,
solar_system_target_id: location.solar_system_id
})
end
end
case WandererApp.Map.check_connection(map_id, location, old_location) do
:ok ->
connection_type =
@@ -356,37 +380,19 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
@connection_type_wormhole
end
# Check if either system is C1 before creating the connection
{:ok, source_system_info} = get_system_static_info(old_location.solar_system_id)
{:ok, target_system_info} = get_system_static_info(location.solar_system_id)
# Set ship size type based on system classes and special rules
ship_size_type =
get_ship_size_type(
old_location.solar_system_id,
location.solar_system_id,
connection_type
)
time_status =
if connection_type == @connection_type_wormhole do
cond do
# C1 systems always get medium
source_system_info.system_class == @c1 or target_system_info.system_class == @c1 ->
@medium_ship_size
# C13 systems always get frigate
source_system_info.system_class == @c13 or target_system_info.system_class == @c13 ->
@frigate_ship_size
# C4 to null gets frigate (unless C4 is shattered)
(source_system_info.system_class == @c4 and target_system_info.system_class == @ns and
not source_system_info.is_shattered) or
(target_system_info.system_class == @c4 and
source_system_info.system_class == @ns and
not target_system_info.is_shattered) ->
@frigate_ship_size
true ->
# Default to large for other wormhole connections
@large_ship_size
end
@connection_time_status_eol_24
else
# Default to large for non-wormhole connections
@large_ship_size
@connection_time_status_default
end
{:ok, connection} =
@@ -395,7 +401,8 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
solar_system_source: old_location.solar_system_id,
solar_system_target: location.solar_system_id,
type: connection_type,
ship_size_type: ship_size_type
ship_size_type: ship_size_type,
time_status: time_status
})
if connection_type == @connection_type_wormhole do
@@ -424,6 +431,20 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
{:ok, character} = WandererApp.Character.get_character(character_id)
if not is_manual do
:telemetry.execute([:wanderer_app, :map, :character, :jump], %{count: 1}, %{})
{:ok, _} =
WandererApp.Api.MapChainPassages.new(%{
map_id: map_id,
character_id: character_id,
ship_type_id: character.ship,
ship_name: character.ship_name,
solar_system_source_id: old_location.solar_system_id,
solar_system_target_id: location.solar_system_id
})
end
{:ok, _} =
WandererApp.User.ActivityTracker.track_map_event(:map_connection_added, %{
character_id: character_id,
@@ -470,12 +491,12 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
end
end
def set_start_time(map_id, connection_id, start_time) do
WandererApp.Cache.put(
"map_#{map_id}:conn_#{connection_id}:start_time",
start_time
)
end
def set_start_time(map_id, connection_id, start_time),
do:
WandererApp.Cache.put(
"map_#{map_id}:conn_#{connection_id}:start_time",
start_time
)
def can_add_location(_scope, nil), do: false
@@ -668,4 +689,79 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
state
end
end
defp get_ship_size_type(
source_solar_system_id,
target_solar_system_id,
@connection_type_wormhole
) do
# Check if either system is C1 before creating the connection
{:ok, source_system_info} = get_system_static_info(source_solar_system_id)
{:ok, target_system_info} = get_system_static_info(target_solar_system_id)
cond do
# C1 systems always get medium
source_system_info.system_class == @c1 or target_system_info.system_class == @c1 ->
@medium_ship_size
# C13 systems always get frigate
source_system_info.system_class == @c13 or target_system_info.system_class == @c13 ->
@frigate_ship_size
# C4 to null gets frigate (unless C4 is shattered)
(source_system_info.system_class == @c4 and target_system_info.system_class == @ns and
not source_system_info.is_shattered) or
(target_system_info.system_class == @c4 and
source_system_info.system_class == @ns and
not target_system_info.is_shattered) ->
@frigate_ship_size
true ->
# Default to large for other wormhole connections
@large_ship_size
end
end
# Default to large for non-wormhole connections
defp get_ship_size_type(_source_solar_system_id, _target_solar_system_id, _connection_type),
do: @large_ship_size
defp get_new_time_status(_start_time, @connection_time_status_default),
do: @connection_time_status_eol_24
defp get_new_time_status(start_time, old_time_status) do
left_minutes =
get_time_status_minutes(old_time_status) -
DateTime.diff(DateTime.utc_now(), start_time, :minute)
cond do
left_minutes <= @connection_eol_minutes ->
@connection_time_status_eol
left_minutes <= @connection_eol_4_minutes ->
@connection_time_status_eol_4
left_minutes <= @connection_eol_4_5_minutes ->
@connection_time_status_eol_4_5
left_minutes <= @connection_eol_16_minutes ->
@connection_time_status_eol_16
left_minutes <= @connection_eol_24_minutes ->
@connection_time_status_eol_24
left_minutes <= @connection_eol_48_minutes ->
@connection_time_status_eol_48
true ->
@connection_time_status_default
end
end
defp get_time_status_minutes(@connection_time_status_eol), do: @connection_eol_minutes
defp get_time_status_minutes(@connection_time_status_eol_4), do: @connection_eol_4_minutes
defp get_time_status_minutes(@connection_time_status_eol_4_5), do: @connection_eol_4_5_minutes
defp get_time_status_minutes(@connection_time_status_eol_16), do: @connection_eol_16_minutes
defp get_time_status_minutes(@connection_time_status_eol_24), do: @connection_eol_24_minutes
defp get_time_status_minutes(@connection_time_status_eol_48), do: @connection_eol_48_minutes
end

View File

@@ -26,7 +26,7 @@ defmodule WandererApp.Map.Server.Impl do
@systems_cleanup_timeout :timer.minutes(30)
@characters_cleanup_timeout :timer.minutes(5)
@connections_cleanup_timeout :timer.minutes(2)
@connections_cleanup_timeout :timer.minutes(1)
@pubsub_client Application.compile_env(:wanderer_app, :pubsub_client)
@backup_state_timeout :timer.minutes(1)
@@ -104,7 +104,7 @@ defmodule WandererApp.Map.Server.Impl do
)
Process.send_after(self(), :update_presence, @update_presence_timeout)
Process.send_after(self(), :cleanup_connections, 5_000)
Process.send_after(self(), :cleanup_connections, @connections_cleanup_timeout)
Process.send_after(self(), :cleanup_systems, 10_000)
Process.send_after(self(), :cleanup_characters, @characters_cleanup_timeout)
Process.send_after(self(), :backup_state, @backup_state_timeout)

View File

@@ -26,23 +26,74 @@
</div>
</div>
</div>
<div class="carousel carousel-center bg-neutral rounded-box max-w-[80%] space-x-4 p-4">
<%= for post <- @posts do %>
<.link class="group carousel-item relative" navigate={~p"/news/#{post.id}"}>
<div class="artboard-horizontal phone-1 relative hover:text-white mt-10">
<img
class="rounded-lg shadow-lg block !w-[400px] !h-[200px] opacity-75"
src={post.cover_image_uri}
/>
<div class="absolute top-0 left-0 w-full h-full bg-gradient-to-b from-transparent to-black opacity-75 group-hover:opacity-25 transition-opacity duration-300">
<div
id="posts-container"
class="bg-neutral rounded-box max-w-[90%] p-4 max-h-[60vh] overflow-y-auto"
>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
<%= for post <- @posts do %>
<.link class="group carousel-item relative" navigate={~p"/news/#{post.id}"}>
<div class="artboard-horizontal phone-1 relative hover:text-white">
<img
class="rounded-lg shadow-lg block !w-[300px] !h-[180px] opacity-75"
src={post.cover_image_uri}
/>
<div class="absolute rounded-lg top-0 left-0 w-full h-full bg-gradient-to-b from-transparent to-black opacity-75 group-hover:opacity-25 transition-opacity duration-300">
</div>
<h3 class="absolute bottom-4 left-14 !text-md font-bold break-normal pt-6 pb-2 ccp-font text-white">
{post.title}
</h3>
</div>
<h3 class="absolute bottom-4 left-14 font-bold break-normal pt-6 pb-2 ccp-font text-white">
{post.title}
</h3>
</div>
</.link>
<% end %>
</.link>
<% end %>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const postsContainer = document.getElementById('posts-container');
if (!postsContainer) return;
let scrollSpeed = 0.5; // pixels per frame
let isScrolling = true;
let scrollDirection = 1; // 1 for down, -1 for up
function autoScroll() {
if (!isScrolling) return;
const maxScroll = postsContainer.scrollHeight - postsContainer.clientHeight;
if (maxScroll <= 0) return; // No need to scroll if content fits
postsContainer.scrollTop += scrollSpeed * scrollDirection;
// Reverse direction when reaching top or bottom
if (postsContainer.scrollTop >= maxScroll) {
scrollDirection = -1;
} else if (postsContainer.scrollTop <= 0) {
scrollDirection = 1;
}
requestAnimationFrame(autoScroll);
}
// Pause scrolling on hover
postsContainer.addEventListener('mouseenter', () => {
isScrolling = false;
});
// Resume scrolling when mouse leaves
postsContainer.addEventListener('mouseleave', () => {
isScrolling = true;
requestAnimationFrame(autoScroll);
});
// Start autoscroll after a delay
setTimeout(() => {
requestAnimationFrame(autoScroll);
}, 2000);
});
</script>
<%!-- <div class="carousel carousel-center !bg-neutral rounded-box max-w-4xl space-x-6 p-4">
</div> --%>

View File

@@ -34,7 +34,11 @@
</button>
</:action>
</.table>
<.link :if={@allow_acl_creation} class="btn mt-2 w-full btn-neutral rounded-none" patch={~p"/access-lists/new"}>
<.link
:if={@allow_acl_creation}
class="btn mt-2 w-full btn-neutral rounded-none"
patch={~p"/access-lists/new"}
>
<.icon name="hero-plus-solid" class="w-6 h-6" />
<h3 class="card-title text-center text-md">New Access List</h3>
</.link>
@@ -142,10 +146,10 @@
placeholder="Select an owner"
options={Enum.map(@characters, fn character -> {character.label, character.id} end)}
/>
<!-- Divider between above inputs and the API key section -->
<hr class="my-4 border-gray-600" />
<!-- API Key Section with grid layout -->
<div class="mt-2">
<label class="block text-sm font-medium text-gray-200 mb-1">ACL API key</label>

View File

@@ -3,7 +3,7 @@ defmodule WandererApp.MixProject do
@source_url "https://github.com/wanderer-industries/wanderer"
@version "1.77.19"
@version "1.78.1"
def project do
[

View File

@@ -1,7 +1,7 @@
%{
title: "Guide: Systems and Connections API",
author: "Wanderer Team",
cover_image_uri: "/images/news/05-07-systems/api-endpoints.png",
cover_image_uri: "/images/news/03-05-api/swagger-ui.png",
tags: ~w(api map systems connections documentation),
description: "Detailed guide for Wanderer's systems and connections API endpoints, including batch operations, updates, and deletions."
}
@@ -912,4 +912,4 @@ If you have questions about these endpoints or need assistance, please reach out
Fly safe,
**The Wanderer Team**
----
----