Compare commits

...

26 Commits

Author SHA1 Message Date
CI
5eafe59dcb chore: release version v1.43.9
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-01-30 21:55:35 +00:00
Dmitry Popov
b38bcaa8cf fix(Core): Add discord link to 'Like' icon on main interface 2025-01-30 22:55:04 +01:00
CI
8a238a447d chore: release version v1.43.8
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-01-26 16:21:18 +00:00
Dmitry Popov
3731219216 fix(Core): Update shuttered constellations (required EVE DB data update on server). 2025-01-26 17:20:28 +01:00
CI
73d5fd5f67 chore: release version v1.43.7 2025-01-26 16:12:32 +00:00
Dmitry Popov
e8e4aed6d5 Signature EOL status support (#136)
* feat(Map): Added an ability to mark signature as EOL

* chore: release version v1.39.1

* fix(Map): Add correct styles for switch

* fix(Map): Refactor signatures code. Add ability to set EOL for signature marked as EOL

* feat(Map): Added EOL status for unsplashed signatures. Show precise time for connection passages on hover.

---------

Co-authored-by: achichenkov <aleksei.chichenkov@telleqt.ai>
2025-01-26 20:12:07 +04:00
CI
63571a462f chore: release version v1.43.6
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-01-22 17:41:42 +00:00
Dmitry Popov
606add4142 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-01-22 18:40:47 +01:00
Dmitry Popov
dac480b059 fix(Widgets): Fix widgets not visible on map 2025-01-22 18:40:37 +01:00
CI
5f67cb1dd7 chore: release version v1.43.5 2025-01-22 17:07:37 +00:00
Dmitry Popov
5886fff753 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-01-22 18:06:22 +01:00
Dmitry Popov
da2e12bdd1 fix(Audit): Fix signature added/removed system name 2025-01-22 18:06:03 +01:00
CI
05c3d20e56 chore: release version v1.43.4
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-01-21 08:48:20 +00:00
guarzo
4633d26517 fix: improve structure widget styling (#127) 2025-01-21 12:47:40 +04:00
CI
30b0556d47 chore: release version v1.43.3 2025-01-21 08:46:40 +00:00
Dmitry Popov
e094378dc5 chore: release version v1.43.2 2025-01-21 09:46:09 +01:00
CI
0c48189503 chore: release version v1.43.2 2025-01-21 07:50:24 +00:00
guarzo
a5c346627a fix: prevent constraint error for follow/toggle (#132) 2025-01-21 11:49:58 +04:00
CI
4e526040bf chore: release version v1.43.1
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/arm64) (push) Blocked by required conditions
Build / merge (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2025-01-20 23:28:45 +00:00
Dmitry Popov
869c25cd60 chore: release version v1.42.4 2025-01-21 00:27:49 +01:00
CI
6aac698cd8 chore: release version v1.43.0 2025-01-20 23:04:11 +00:00
guarzo
230016b90f feat: add news post for structures widget (#131) 2025-01-21 03:03:31 +04:00
CI
4b1aef8dd9 chore: release version v1.42.5 2025-01-20 23:01:42 +00:00
Dmitry Popov
d34509d7a0 fix(Map): Fix link signatures on splash. Fix deleting connection on locked system remove. 2025-01-20 23:59:58 +01:00
CI
fca98ec232 chore: release version v1.42.4
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/arm64) (push) Blocked by required conditions
Build / merge (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2025-01-20 10:43:19 +00:00
Dmitry Popov
e2814e95bd fix: Fix system statics list (required EVE DB data update). Add system name to signature added/removed audit log 2025-01-20 11:42:38 +01:00
40 changed files with 759 additions and 277 deletions

View File

@@ -2,6 +2,102 @@
<!-- changelog -->
## [v1.43.9](https://github.com/wanderer-industries/wanderer/compare/v1.43.8...v1.43.9) (2025-01-30)
### Bug Fixes:
* Core: Add discord link to 'Like' icon on main interface
## [v1.43.8](https://github.com/wanderer-industries/wanderer/compare/v1.43.7...v1.43.8) (2025-01-26)
### Bug Fixes:
* Core: Update shuttered constellations (required EVE DB data update on server).
## [v1.43.7](https://github.com/wanderer-industries/wanderer/compare/v1.43.6...v1.43.7) (2025-01-26)
## [v1.43.6](https://github.com/wanderer-industries/wanderer/compare/v1.43.5...v1.43.6) (2025-01-22)
### Bug Fixes:
* Widgets: Fix widgets not visible on map
## [v1.43.5](https://github.com/wanderer-industries/wanderer/compare/v1.43.4...v1.43.5) (2025-01-22)
### Bug Fixes:
* Audit: Fix signature added/removed system name
## [v1.43.4](https://github.com/wanderer-industries/wanderer/compare/v1.43.3...v1.43.4) (2025-01-21)
### Bug Fixes:
* improve structure widget styling (#127)
## [v1.43.3](https://github.com/wanderer-industries/wanderer/compare/v1.43.2...v1.43.3) (2025-01-21)
## [v1.43.2](https://github.com/wanderer-industries/wanderer/compare/v1.43.1...v1.43.2) (2025-01-21)
### Bug Fixes:
* prevent constraint error for follow/toggle (#132)
## [v1.43.1](https://github.com/wanderer-industries/wanderer/compare/v1.43.0...v1.43.1) (2025-01-20)
## [v1.43.0](https://github.com/wanderer-industries/wanderer/compare/v1.42.5...v1.43.0) (2025-01-20)
### Features:
* add news post for structures widget (#131)
## [v1.42.5](https://github.com/wanderer-industries/wanderer/compare/v1.42.4...v1.42.5) (2025-01-20)
### Bug Fixes:
* Map: Fix link signatures on splash. Fix deleting connection on locked system remove.
## [v1.42.4](https://github.com/wanderer-industries/wanderer/compare/v1.42.3...v1.42.4) (2025-01-20)
### Bug Fixes:
* Fix system statics list (required EVE DB data update). Add system name to signature added/removed audit log
## [v1.42.3](https://github.com/wanderer-industries/wanderer/compare/v1.42.2...v1.42.3) (2025-01-17)

View File

@@ -112,3 +112,28 @@
.p-autocomplete .p-autocomplete-multiple-container .p-autocomplete-token {
margin-right: 0 !important;
}
/* Fixed sizes of Input switch */
.p-inputswitch {
width: 2.0rem;
height: 1.15rem;
.p-inputswitch-slider:before {
width: 0.8rem;
height: 0.8rem;
left: 0.14rem;
margin-top: -0.385rem;
}
&.p-highlight .p-inputswitch-slider:before {
transform: translateX(0.8rem);
}
&:not(.p-disabled):has(.p-inputswitch-input:hover) .p-inputswitch-slider {
background: rgb(255 255 255 / 21%);
}
&.p-highlight .p-inputswitch-slider {
background: #966d3d;
}
}

View File

@@ -2,6 +2,7 @@ import { ForwardedRef, forwardRef, MouseEvent, useCallback, useEffect, useMemo }
import ReactFlow, {
Background,
Edge,
EdgeChange,
MiniMap,
Node,
NodeChange,
@@ -83,6 +84,7 @@ interface MapCompProps {
onCommand: OutCommandHandler;
onSelectionChange: OnMapSelectionChange;
onManualDelete(systems: string[]): void;
canRemoveConnection?(connectionId: string): boolean;
onConnectionInfoClick?(e: SolarSystemConnection): void;
onAddSystem?: OnMapAddSystemCallback;
onSelectionContextMenu?: NodeSelectionMouseHandler;
@@ -112,8 +114,9 @@ const MapComp = ({
isSoftBackground,
theme,
onAddSystem,
canRemoveConnection,
}: MapCompProps) => {
const { getNode, getNodes } = useReactFlow();
const { getEdge, getNode, getNodes } = useReactFlow();
const [nodes, , onNodesChange] = useNodesState<Node<SolarSystemRawType>>(initialNodes);
const [edges, , onEdgesChange] = useEdgesState<Edge<SolarSystemConnection>>(initialEdges);
@@ -222,6 +225,40 @@ const MapComp = ({
[getNode, getNodes, onManualDelete, onNodesChange],
);
const handleEdgesChange = useCallback(
(changes: EdgeChange[]) => {
const nextChanges = changes.reduce((acc, change) => {
if (change.type !== 'remove') {
return [...acc, change];
}
if (canRemoveConnection?.(change.id)) {
return [...acc, change];
}
const edge = getEdge(change.id);
if (!edge) {
return [...acc, change];
}
const sourceNode = getNode(edge.source);
const targetNode = getNode(edge.target);
if (!sourceNode || !targetNode) {
return [...acc, change];
}
if (sourceNode.data.locked || targetNode.data.locked) {
return acc;
}
return [...acc, change];
}, [] as EdgeChange[]);
onEdgesChange(nextChanges);
},
[getEdge, getNode, onEdgesChange],
);
useEffect(() => {
update(x => ({
...x,
@@ -237,7 +274,7 @@ const MapComp = ({
nodes={nodes}
edges={edges}
onNodesChange={handleNodesChange}
onEdgesChange={onEdgesChange}
onEdgesChange={handleEdgesChange}
onConnect={onConnect}
// TODO we need save into session all of this
// and on any action do either

View File

@@ -15,4 +15,8 @@
font-weight: bolder;
display: block;
}
& > .Eol {
display: block;
}
}

View File

@@ -8,8 +8,8 @@ import { WORMHOLE_CLASS_STYLES, WORMHOLES_ADDITIONAL_INFO } from '@/hooks/Mapper
import { useMemo } from 'react';
import clsx from 'clsx';
import { renderInfoColumn } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders';
import { k162Types } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureK162TypeSelect';
import { K162_TYPES_MAP } from '@/hooks/Mapper/constants.ts';
import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo.ts';
interface UnsplashedSignatureProps {
signature: SystemSignature;
@@ -22,17 +22,22 @@ export const UnsplashedSignature = ({ signature }: UnsplashedSignatureProps) =>
const whData = useMemo(() => wormholesData[signature.type], [signature.type, wormholesData]);
const whClass = useMemo(() => (whData ? WORMHOLES_ADDITIONAL_INFO[whData.dest] : null), [whData]);
const k162TypeOption = useMemo(() => {
if (!signature.custom_info) {
return null;
}
const customInfo = JSON.parse(signature.custom_info);
if (!customInfo.k162Type) {
return null;
}
return k162Types.find(x => x.value === customInfo.k162Type);
const customInfo = useMemo(() => {
return parseSignatureCustomInfo(signature.custom_info);
}, [signature]);
const k162TypeOption = useMemo(() => {
if (!customInfo?.k162Type) {
return null;
}
return K162_TYPES_MAP[customInfo.k162Type];
}, [customInfo]);
const isEOL = useMemo(() => {
return customInfo?.isEOL;
}, [customInfo]);
const whClassStyle = useMemo(() => {
if (signature.type === 'K162' && k162TypeOption) {
const k162Data = wormholesData[k162TypeOption.whClassName];
@@ -45,19 +50,19 @@ export const UnsplashedSignature = ({ signature }: UnsplashedSignatureProps) =>
return (
<WdTooltipWrapper
className={clsx(classes.Signature)}
// @ts-ignore
content={
(
<div className="flex flex-col gap-1">
<InfoDrawer title={<b className="text-slate-50">{signature.eve_id}</b>}>
{renderInfoColumn(signature)}
</InfoDrawer>
</div>
) as React.ReactNode
<div className="flex flex-col gap-1">
<InfoDrawer title={<b className="text-slate-50">{signature.eve_id}</b>}>
{renderInfoColumn(signature)}
</InfoDrawer>
</div>
}
>
<div className={clsx(classes.Box, whClassStyle)}>
<svg width="13" height="4" viewBox="0 0 13 4" xmlns="http://www.w3.org/2000/svg">
<rect width="13" height="4" rx="2" className={whClassStyle} fill="currentColor" />
<svg width="13" height="8" viewBox="0 0 13 8" xmlns="http://www.w3.org/2000/svg">
<rect y="1" width="13" height="4" rx="2" className={whClassStyle} fill="currentColor" />
{isEOL && <rect x="4" width="5" height="6" rx="1" className={clsx(classes.Eol)} fill="#a153ac" />}
</svg>
</div>
</WdTooltipWrapper>

View File

@@ -70,7 +70,7 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
setTimeout(() => addConnections(data as CommandAddConnections), 100);
break;
case Commands.removeConnections:
removeConnections(data as CommandRemoveConnections);
setTimeout(() => removeConnections(data as CommandRemoveConnections), 100);
break;
case Commands.charactersUpdated:
charactersUpdated(data as CommandCharactersUpdated);

View File

@@ -1,28 +1,29 @@
import { PrimeIcons } from 'primereact/api';
import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
import { SystemViewStandalone, WHClassView } from '@/hooks/Mapper/components/ui-kit';
import { SystemViewStandalone, TooltipPosition, WHClassView } from '@/hooks/Mapper/components/ui-kit';
import {
k162Types,
renderK162Type,
} from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureK162TypeSelect';
import { renderK162Type } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureK162TypeSelect';
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
import clsx from 'clsx';
import { renderName } from './renderName.tsx';
import { K162_TYPES_MAP } from '@/hooks/Mapper/constants.ts';
import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo.ts';
export const renderInfoColumn = (row: SystemSignature) => {
if (!row.group || row.group === SignatureGroup.Wormhole) {
let k162TypeOption = null;
if (row.custom_info) {
const customInfo = JSON.parse(row.custom_info);
if (customInfo.k162Type) {
k162TypeOption = k162Types.find(x => x.value === customInfo.k162Type);
}
}
const customInfo = parseSignatureCustomInfo(row.custom_info);
const k162TypeOption = customInfo.k162Type ? K162_TYPES_MAP[customInfo.k162Type] : null;
return (
<div className="flex justify-start items-center gap-[4px]">
{customInfo.isEOL && (
<WdTooltipWrapper offset={5} position={TooltipPosition.top} content="Signature marked as EOL">
<div className="pi pi-clock text-fuchsia-400 text-[11px] mr-[2px]"></div>
</WdTooltipWrapper>
)}
{row.type && (
<WHClassView
className="text-[11px]"
@@ -34,7 +35,7 @@ export const renderInfoColumn = (row: SystemSignature) => {
/>
)}
{!row.linked_system && row.type === 'K162' && !!k162TypeOption && <>{renderK162Type(k162TypeOption)}</>}
{!row.linked_system && row.type === 'K162' && k162TypeOption && renderK162Type(k162TypeOption)}
{row.linked_system && (
<>

View File

@@ -70,6 +70,11 @@ export const SystemStructures: React.FC = () => {
<WdImgButton
className={`${PrimeIcons.CLOCK} text-sky-400 hover:text-sky-200 transition duration-300`}
onClick={handlePasteTimer}
tooltip={{
position: TooltipPosition.left,
// @ts-ignore
content: 'Add Structures/Timer',
}}
/>
<WdImgButton
className={PrimeIcons.QUESTION_CIRCLE}

View File

@@ -1,18 +1,24 @@
.TableRowCompact {
height: 8px;
max-height: 8px;
font-size: 12px !important;
line-height: 8px;
}
.Table {
font-size: 12px;
border-collapse: collapse;
}
.TableRowCompact {
height: 8px;
max-height: 8px;
font-size: 12px !important;
line-height: 8px;
}
.Tooltip {
white-space: pre-line; // or pre-wrap
line-height: 1.2rem;
}
.Table {
font-size: 12px;
border-collapse: collapse;
table-layout: fixed;
width: 100%;
}
.Table .p-datatable-tbody > tr > td {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.Tooltip {
white-space: pre-line;
line-height: 1.2rem;
}

View File

@@ -63,6 +63,7 @@ export const SystemStructuresContent: React.FC<SystemStructuresContentProps> = (
size="small"
sortMode="single"
rowHover
style={{ tableLayout: 'fixed', width: '100%' }}
onRowClick={handleRowClick}
onRowDoubleClick={handleRowDoubleClick}
rowClassName={rowData => {
@@ -74,11 +75,56 @@ export const SystemStructuresContent: React.FC<SystemStructuresContentProps> = (
);
}}
>
<Column header="Type" body={renderTypeCell} style={{ width: '160px' }} />
<Column field="name" header="Name" style={{ width: '120px' }} />
<Column header="Owner" body={renderOwnerCell} style={{ width: '120px' }} />
<Column field="status" header="Status" style={{ width: '100px' }} />
<Column header="Timer" body={renderTimerCell} style={{ width: '110px' }} />
<Column
header="Type"
body={renderTypeCell}
style={{
width: '160px',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
/>
<Column
field="name"
header="Name"
style={{
width: '120px',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
/>
<Column
header="Owner"
body={renderOwnerCell}
style={{
width: '120px',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
/>
<Column
field="status"
header="Status"
style={{
width: '100px',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
/>
<Column
header="Timer"
body={renderTimerCell}
style={{
width: '110px',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
/>
<Column
body={(rowData: StructureItem) => (
<i
@@ -90,7 +136,13 @@ export const SystemStructuresContent: React.FC<SystemStructuresContentProps> = (
}}
/>
)}
style={{ width: '40px', textAlign: 'center' }}
style={{
width: '40px',
textAlign: 'center',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
/>
</DataTable>
</div>

View File

@@ -2,7 +2,9 @@ import clsx from 'clsx';
import classes from './PassageCard.module.scss';
import { Passage } from '@/hooks/Mapper/types';
import { TimeAgo } from '@/hooks/Mapper/components/ui-kit';
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
import { kgToTons } from '@/hooks/Mapper/utils/kgToTons.ts';
import { useMemo } from 'react';
type PassageCardType = {
// compact?: boolean;
@@ -26,6 +28,11 @@ export const getShipName = (name: string) => {
export const PassageCard = ({ inserted_at, character: char, ship }: PassageCardType) => {
const isOwn = false;
const insertedAt = useMemo(() => {
const date = new Date(inserted_at);
return date.toLocaleString();
}, [inserted_at]);
return (
<div className={clsx(classes.CharacterCard, 'w-full text-xs', 'flex flex-col box-border')}>
<div className="flex flex-col justify-between px-2 py-1 gap-1">
@@ -76,7 +83,9 @@ export const PassageCard = ({ inserted_at, character: char, ship }: PassageCardT
{/*time and class*/}
<div className="flex justify-between">
<span className="text-stone-400">
<TimeAgo timestamp={inserted_at} />
<WdTooltipWrapper content={insertedAt}>
<TimeAgo timestamp={inserted_at} />
</WdTooltipWrapper>
</span>
<div className="text-stone-400">{kgToTons(parseInt(ship.ship_type_info.mass))}</div>

View File

@@ -1,4 +1,5 @@
import styles from './MapSettings.module.scss';
import { WdCheckbox } from '@/hooks/Mapper/components/ui-kit';
interface PrettySwitchboxProps {

View File

@@ -53,6 +53,7 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
...out,
custom_info: JSON.stringify({
k162Type: values.k162Type,
isEOL: values.isEOL,
}),
};
@@ -127,14 +128,17 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
const { linked_system, custom_info, ...rest } = signatureData;
let k162Type = null;
let isEOL = false;
if (custom_info) {
const customInfo = JSON.parse(custom_info);
k162Type = customInfo.k162Type;
isEOL = customInfo.isEOL;
}
signatureForm.reset({
linked_system: linked_system?.solar_system_id.toString() ?? undefined,
k162Type: k162Type,
isEOL: isEOL,
...rest,
});
}, [signatureForm, signatureData]);

View File

@@ -0,0 +1,24 @@
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

@@ -0,0 +1 @@
export * from './SignatureEOLCheckbox.tsx';

View File

@@ -3,6 +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';
export const SignatureGroupContentWormholes = () => {
const { watch } = useFormContext<SystemSignature>();
@@ -26,6 +27,11 @@ export const SignatureGroupContentWormholes = () => {
<span>Leads To:</span>
<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>
</>
);
};

View File

@@ -3,100 +3,8 @@ import clsx from 'clsx';
import { Controller, useFormContext } from 'react-hook-form';
import { useMemo } from 'react';
import { SystemSignature } from '@/hooks/Mapper/types';
import { WHClassView } from '@/hooks/Mapper/components/ui-kit';
export const k162Types = [
{
label: 'Hi-Sec',
value: 'hs',
whClassName: 'A641',
},
{
label: 'Low-Sec',
value: 'ls',
whClassName: 'J377',
},
{
label: 'Null-Sec',
value: 'ns',
whClassName: 'C248',
},
{
label: 'C1',
value: 'c1',
whClassName: 'E004',
},
{
label: 'C2',
value: 'c2',
whClassName: 'D382',
},
{
label: 'C3',
value: 'c3',
whClassName: 'L477',
},
{
label: 'C4',
value: 'c4',
whClassName: 'M001',
},
{
label: 'C5',
value: 'c5',
whClassName: 'L614',
},
{
label: 'C6',
value: 'c6',
whClassName: 'G008',
},
{
label: 'C13',
value: 'c13',
whClassName: 'A009',
},
{
label: 'Thera',
value: 'thera',
whClassName: 'F353',
},
{
label: 'Pochven',
value: 'pochven',
whClassName: 'F216',
},
];
const renderNoValue = () => <div className="flex gap-2 items-center">-Unknown-</div>;
// @ts-ignore
export const renderK162Type = (option: {
label?: string;
value: string;
security?: string;
system_class?: number;
whClassName?: string;
}) => {
if (!option) {
return renderNoValue();
}
const { value, whClassName = '' } = option;
if (value == null) {
return renderNoValue();
}
return (
<WHClassView
classNameWh="!text-[11px] !font-bold"
hideWhClassName
hideTooltip
whClassName={whClassName}
noOffset
useShortTitle
/>
);
};
import { K162_TYPES } from '@/hooks/Mapper/constants.ts';
import { renderK162Type } from '.';
export interface SignatureK162TypeSelectProps {
name: string;
@@ -107,7 +15,7 @@ export const SignatureK162TypeSelect = ({ name, defaultValue = '' }: SignatureK1
const { control } = useFormContext<SystemSignature>();
const options = useMemo(() => {
return [{ value: null }, ...k162Types];
return [{ value: null }, ...K162_TYPES];
}, []);
return (

View File

@@ -1 +1,2 @@
export * from './SignatureK162TypeSelect.tsx';
export * from './renderK162Type.tsx';

View File

@@ -0,0 +1,26 @@
import { WHClassView } from '@/hooks/Mapper/components/ui-kit';
import { K162Type } from '@/hooks/Mapper/constants.ts';
const renderNoValue = () => <div className="flex gap-2 items-center">-Unknown-</div>;
export const renderK162Type = (option: K162Type) => {
if (!option) {
return renderNoValue();
}
const { value, whClassName = '' } = option;
if (value == null) {
return renderNoValue();
}
return (
<WHClassView
classNameWh="!text-[11px] !font-bold"
hideWhClassName
hideTooltip
whClassName={whClassName}
noOffset
useShortTitle
/>
);
};

View File

@@ -14,9 +14,10 @@ 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';
import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts';
import { Node, XYPosition } from 'reactflow';
import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts';
import { useCommandsSystems } from '@/hooks/Mapper/mapRootProvider/hooks/api';
import { emitMapEvent, useMapEventListener } from '@/hooks/Mapper/events';
import { STORED_INTERFACE_DEFAULT_VALUES } from '@/hooks/Mapper/mapRootProvider/MapRootProvider';
@@ -32,7 +33,7 @@ export const MapWrapper = () => {
const {
update,
outCommand,
data: { selectedConnections, selectedSystems, hubs, systems },
data: { selectedConnections, selectedSystems, hubs, systems, connections, linkSignatureToSystem },
interfaceSettings: {
isShowMenu,
isShowMinimap = STORED_INTERFACE_DEFAULT_VALUES.isShowMinimap,
@@ -46,25 +47,19 @@ export const MapWrapper = () => {
const { deleteSystems } = useDeleteSystems();
const { mapRef, runCommand } = useCommonMapEventProcessor();
const { updateLinkSignatureToSystem } = useCommandsSystems();
const { open, ...systemContextProps } = useContextMenuSystemHandlers({ systems, hubs, outCommand });
const { handleSystemMultipleContext, ...systemMultipleCtxProps } = useContextMenuSystemMultipleHandlers();
const [openSettings, setOpenSettings] = useState<string | null>(null);
const [openLinkSignatures, setOpenLinkSignatures] = useState<any | null>(null);
const [openCustomLabel, setOpenCustomLabel] = useState<string | null>(null);
const [openAddSystem, setOpenAddSystem] = useState<XYPosition | null>(null);
const [selectedConnection, setSelectedConnection] = useState<SolarSystemConnection | null>(null);
const ref = useRef({ selectedConnections, selectedSystems, systemContextProps, systems, deleteSystems });
ref.current = { selectedConnections, selectedSystems, systemContextProps, systems, deleteSystems };
const ref = useRef({ selectedConnections, selectedSystems, systemContextProps, systems, connections, deleteSystems });
ref.current = { selectedConnections, selectedSystems, systemContextProps, systems, connections, deleteSystems };
useMapEventListener(event => {
switch (event.name) {
case Commands.linkSignatureToSystem:
setOpenLinkSignatures(event.data);
return true;
}
runCommand(event);
});
@@ -130,6 +125,11 @@ export const MapWrapper = () => {
setOpenAddSystem(coordinates);
}, []);
const canRemoveConnection = useCallback((connectionId: string) => {
const { connections } = ref.current;
return !connections.some(x => x.id === connectionId);
}, []);
const handleSubmitAddSystem: SearchOnSubmitCallback = useCallback(
async item => {
if (ref.current.systems.some(x => x.system_static_info.solar_system_id === item.value)) {
@@ -166,6 +166,7 @@ export const MapWrapper = () => {
isSoftBackground={isSoftBackground}
theme={theme}
onAddSystem={onAddSystem}
canRemoveConnection={canRemoveConnection}
/>
{openSettings != null && (
@@ -176,8 +177,8 @@ export const MapWrapper = () => {
<SystemCustomLabelDialog systemId={openCustomLabel} visible setVisible={() => setOpenCustomLabel(null)} />
)}
{openLinkSignatures != null && (
<SystemLinkSignatureDialog data={openLinkSignatures} setVisible={() => setOpenLinkSignatures(null)} />
{linkSignatureToSystem != null && (
<SystemLinkSignatureDialog data={linkSignatureToSystem} setVisible={() => updateLinkSignatureToSystem(null)} />
)}
<AddSystemDialog

View File

@@ -384,6 +384,10 @@ export const WindowManager: React.FC<WindowManagerProps> = ({ windows: initialWi
next.position.y = container.clientHeight - next.size.height - SNAP_GAP;
}
if (next.position.y < 0) {
next.position.y = 0;
}
return next;
});
});

View File

@@ -65,3 +65,77 @@ export const REGIONS_MAP: Record<number, Spaces> = {
[Regions.TashMurkon]: Spaces.Amarr,
[Regions.VergeVendor]: Spaces.Gallente,
};
export type K162Type = {
label: string;
value: string;
whClassName: string;
};
export const K162_TYPES: K162Type[] = [
{
label: 'Hi-Sec',
value: 'hs',
whClassName: 'A641',
},
{
label: 'Low-Sec',
value: 'ls',
whClassName: 'J377',
},
{
label: 'Null-Sec',
value: 'ns',
whClassName: 'C248',
},
{
label: 'C1',
value: 'c1',
whClassName: 'E004',
},
{
label: 'C2',
value: 'c2',
whClassName: 'D382',
},
{
label: 'C3',
value: 'c3',
whClassName: 'L477',
},
{
label: 'C4',
value: 'c4',
whClassName: 'M001',
},
{
label: 'C5',
value: 'c5',
whClassName: 'L614',
},
{
label: 'C6',
value: 'c6',
whClassName: 'G008',
},
{
label: 'C13',
value: 'c13',
whClassName: 'A009',
},
{
label: 'Thera',
value: 'thera',
whClassName: 'F353',
},
{
label: 'Pochven',
value: 'pochven',
whClassName: 'F216',
},
];
export const K162_TYPES_MAP: { [key: string]: K162Type } = K162_TYPES.reduce(
(acc, x) => ({ ...acc, [x.value]: x }),
{},
);

View File

@@ -0,0 +1,9 @@
import { SignatureCustomInfo } from '@/hooks/Mapper/types';
export const parseSignatureCustomInfo = (str: string | undefined): SignatureCustomInfo => {
if (str == null || str === '') {
return {};
}
return JSON.parse(str);
};

View File

@@ -10,10 +10,12 @@ import {
useStoreWidgets,
WindowStoreInfo,
} from '@/hooks/Mapper/mapRootProvider/hooks/useStoreWidgets.ts';
import { CommandLinkSignatureToSystem } from '@/hooks/Mapper/types';
export type MapRootData = MapUnionTypes & {
selectedSystems: string[];
selectedConnections: Pick<SolarSystemConnection, 'source' | 'target'>[];
linkSignatureToSystem: CommandLinkSignatureToSystem | null;
};
const INITIAL_DATA: MapRootData = {
@@ -34,6 +36,7 @@ const INITIAL_DATA: MapRootData = {
selectedConnections: [],
userPermissions: {},
options: {},
linkSignatureToSystem: null,
};
export enum InterfaceStoredSettingsProps {

View File

@@ -1,6 +1,11 @@
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useCallback, useRef } from 'react';
import { CommandAddSystems, CommandRemoveSystems, CommandUpdateSystems } from '@/hooks/Mapper/types';
import {
CommandAddSystems,
CommandRemoveSystems,
CommandUpdateSystems,
CommandLinkSignatureToSystem,
} from '@/hooks/Mapper/types';
import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts';
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
import { emitMapEvent } from '@/hooks/Mapper/events';
@@ -74,5 +79,10 @@ export const useCommandsSystems = () => {
[outCommand],
);
return { addSystems, removeSystems, updateSystems, updateSystemSignatures };
const updateLinkSignatureToSystem = useCallback(async (command: CommandLinkSignatureToSystem) => {
const { update } = ref.current;
update({ linkSignatureToSystem: command }, true);
}, []);
return { addSystems, removeSystems, updateSystems, updateSystemSignatures, updateLinkSignatureToSystem };
};

View File

@@ -32,7 +32,8 @@ import { emitMapEvent } from '@/hooks/Mapper/events';
export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
const mapInit = useMapInit();
const { addSystems, removeSystems, updateSystems, updateSystemSignatures } = useCommandsSystems();
const { addSystems, removeSystems, updateSystems, updateSystemSignatures, updateLinkSignatureToSystem } =
useCommandsSystems();
const { addConnections, removeConnections, updateConnection } = useCommandsConnections();
const { charactersUpdated, characterAdded, characterRemoved, characterUpdated, presentCharacters } =
useCommandsCharacters();
@@ -93,7 +94,9 @@ export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
break;
case Commands.linkSignatureToSystem: // USED
// do nothing here
setTimeout(() => {
updateLinkSignatureToSystem(data as CommandLinkSignatureToSystem);
}, 200);
break;
case Commands.centerSystem: // USED

View File

@@ -26,15 +26,20 @@ export type GroupType = {
h: number;
};
export type SignatureCustomInfo = {
k162Type?: string;
isEOL?: boolean;
};
export type SystemSignature = {
eve_id: string;
kind: SignatureKind;
name: string;
// SignatureCustomInfo
custom_info?: string;
description?: string;
group: SignatureGroup;
type: string;
k162Type?: string;
linked_system?: SolarSystemStaticInfoRaw;
inserted_at?: string;
updated_at?: string;

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -206,16 +206,24 @@ defmodule WandererApp.Map.Server.SystemsImpl do
user_id,
character_id
) do
removed_system_ids =
filtered_ids =
removed_ids
|> Enum.map(fn solar_system_id ->
WandererApp.Map.find_system_by_location(map_id, %{solar_system_id: solar_system_id})
end)
|> Enum.filter(fn system -> not is_nil(system) end)
|> Enum.map(& &1.id)
|> Enum.filter(fn system -> not is_nil(system) && not system.locked end)
|> Enum.map(&{&1.solar_system_id, &1.id})
solar_system_ids_to_remove =
filtered_ids
|> Enum.map(fn {solar_system_id, _} -> solar_system_id end)
system_ids_to_remove =
filtered_ids
|> Enum.map(fn {_, system_id} -> system_id end)
connections_to_remove =
removed_ids
solar_system_ids_to_remove
|> Enum.map(fn solar_system_id ->
WandererApp.Map.find_connections(map_id, solar_system_id)
end)
@@ -223,9 +231,9 @@ defmodule WandererApp.Map.Server.SystemsImpl do
|> Enum.uniq_by(& &1.id)
:ok = WandererApp.Map.remove_connections(map_id, connections_to_remove)
:ok = WandererApp.Map.remove_systems(map_id, removed_ids)
:ok = WandererApp.Map.remove_systems(map_id, solar_system_ids_to_remove)
removed_ids
solar_system_ids_to_remove
|> Enum.each(fn solar_system_id ->
map_id
|> WandererApp.MapSystemRepo.remove_from_map(solar_system_id)
@@ -245,7 +253,7 @@ defmodule WandererApp.Map.Server.SystemsImpl do
WandererApp.MapConnectionRepo.destroy(map_id, connection)
end)
removed_ids
solar_system_ids_to_remove
|> Enum.map(fn solar_system_id ->
WandererApp.Api.MapSystemSignature.by_linked_system_id!(solar_system_id)
end)
@@ -259,7 +267,7 @@ defmodule WandererApp.Map.Server.SystemsImpl do
end)
linked_system_ids =
removed_system_ids
system_ids_to_remove
|> Enum.map(fn system_id ->
WandererApp.Api.MapSystemSignature.by_system_id!(system_id)
|> Enum.filter(fn s -> not is_nil(s.linked_system_id) end)
@@ -276,10 +284,10 @@ defmodule WandererApp.Map.Server.SystemsImpl do
})
end)
@ddrt.delete(removed_ids, rtree_name)
@ddrt.delete(solar_system_ids_to_remove, rtree_name)
Impl.broadcast!(map_id, :remove_connections, connections_to_remove)
Impl.broadcast!(map_id, :systems_removed, removed_ids)
Impl.broadcast!(map_id, :systems_removed, solar_system_ids_to_remove)
case not is_nil(user_id) do
true ->
@@ -288,12 +296,12 @@ defmodule WandererApp.Map.Server.SystemsImpl do
character_id: character_id,
user_id: user_id,
map_id: map_id,
solar_system_ids: removed_ids
solar_system_ids: solar_system_ids_to_remove
})
:telemetry.execute(
[:wanderer_app, :map, :systems, :remove],
%{count: removed_ids |> Enum.count()}
%{count: solar_system_ids_to_remove |> Enum.count()}
)
:ok

View File

@@ -66,16 +66,12 @@ defmodule WandererAppWeb.Layouts do
def feedback_container(assigns) do
~H"""
<div
id="feeback-container"
data-az-l="6e9c41f4-8f3f-4e3b-bbc6-e808f9e46808"
class={[
"flex flex-col p-4 items-center absolute bottom-40 left-1 gap-2 tooltip tooltip-right text-gray-400 hover:text-white"
]}
data-tip="Leave Feedback"
<.link
href="https://discord.gg/cafERvDD2k"
class="flex flex-col p-4 items-center absolute bottom-40 left-1 gap-2 tooltip tooltip-right text-gray-400 hover:text-white"
>
<.icon name="hero-hand-thumb-up-solid" class="h-4 w-4" />
</div>
</.link>
"""
end

View File

@@ -162,40 +162,40 @@ defmodule WandererAppWeb.UserActivity do
"solar_system_id" => solar_system_id,
"value" => value
}) do
system_name = _get_system_name(solar_system_id)
system_name = get_system_name(solar_system_id)
try do
%{"customLabel" => customLabel, "labels" => labels} = Jason.decode!(value)
"#{system_name} labels - #{inspect(labels)}, customLabel - #{customLabel}"
"#{system_name}: labels - #{inspect(labels)}, customLabel - #{customLabel}"
rescue
_ ->
"#{system_name} labels - #{inspect(value)}"
"#{system_name}: labels - #{inspect(value)}"
end
end
defp get_event_data(:system_added, %{
"solar_system_id" => solar_system_id
}),
do: _get_system_name(solar_system_id)
do: get_system_name(solar_system_id)
defp get_event_data(:hub_added, %{
"solar_system_id" => solar_system_id
}),
do: _get_system_name(solar_system_id)
do: get_system_name(solar_system_id)
defp get_event_data(:hub_removed, %{
"solar_system_id" => solar_system_id
}),
do: _get_system_name(solar_system_id)
do: get_system_name(solar_system_id)
defp get_event_data(:system_updated, %{
"key" => key,
"solar_system_id" => solar_system_id,
"value" => value
}) do
system_name = _get_system_name(solar_system_id)
"#{system_name} #{key} - #{inspect(value)}"
system_name = get_system_name(solar_system_id)
"#{system_name}: #{key} - #{inspect(value)}"
end
defp get_event_data(:systems_removed, %{
@@ -203,29 +203,28 @@ defmodule WandererAppWeb.UserActivity do
}),
do:
solar_system_ids
|> Enum.map(&_get_system_name/1)
|> Enum.map(&get_system_name/1)
|> Enum.join(", ")
defp get_event_data(:signatures_added, %{
defp get_event_data(signatures_event, %{
"solar_system_id" => solar_system_id,
"signatures" => signatures
}),
do:
signatures
|> Enum.join(", ")
})
when signatures_event in [:signatures_added, :signatures_removed],
do: "#{get_system_name(solar_system_id)}: #{signatures |> Enum.join(", ")}"
defp get_event_data(:signatures_removed, %{
defp get_event_data(signatures_event, %{
"signatures" => signatures
}),
do:
signatures
|> Enum.join(", ")
})
when signatures_event in [:signatures_added, :signatures_removed],
do: signatures |> Enum.join(", ")
defp get_event_data(:map_connection_added, %{
"solar_system_source_id" => solar_system_source_id,
"solar_system_target_id" => solar_system_target_id
}) do
source_system_name = _get_system_name(solar_system_source_id)
target_system_name = _get_system_name(solar_system_target_id)
source_system_name = get_system_name(solar_system_source_id)
target_system_name = get_system_name(solar_system_target_id)
"[#{source_system_name}:#{target_system_name}]"
end
@@ -233,8 +232,8 @@ defmodule WandererAppWeb.UserActivity do
"solar_system_source_id" => solar_system_source_id,
"solar_system_target_id" => solar_system_target_id
}) do
source_system_name = _get_system_name(solar_system_source_id)
target_system_name = _get_system_name(solar_system_target_id)
source_system_name = get_system_name(solar_system_source_id)
target_system_name = get_system_name(solar_system_target_id)
"[#{source_system_name}:#{target_system_name}]"
end
@@ -244,14 +243,14 @@ defmodule WandererAppWeb.UserActivity do
"solar_system_target_id" => solar_system_target_id,
"value" => value
}) do
source_system_name = _get_system_name(solar_system_source_id)
target_system_name = _get_system_name(solar_system_target_id)
source_system_name = get_system_name(solar_system_source_id)
target_system_name = get_system_name(solar_system_target_id)
"[#{source_system_name}:#{target_system_name}] #{key} - #{inspect(value)}"
end
defp get_event_data(_name, data), do: Jason.encode!(data)
defp _get_system_name(solar_system_id) do
defp get_system_name(solar_system_id) do
case WandererApp.CachedInfo.get_system_static_info(solar_system_id) do
{:ok, nil} ->
solar_system_id

View File

@@ -213,7 +213,7 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
s.character_id in user_char_ids
end)
existing = Enum.find(my_settings, &(&1.character_id == clicked_char_id))
existing = Enum.find(all_settings, &(&1.character_id == clicked_char_id))
{:ok, target_setting} =
if not is_nil(existing) do

View File

@@ -270,6 +270,8 @@ defmodule WandererAppWeb.MapCoreEventHandler do
current_user.characters |> Enum.map(& &1.id)
)
{:ok, map_user_settings} = WandererApp.MapUserSettingsRepo.get(map_id, current_user.id)
{:ok, character_settings} =
case WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id) do
{:ok, settings} -> {:ok, settings}
@@ -302,6 +304,7 @@ defmodule WandererAppWeb.MapCoreEventHandler do
socket
|> assign(
map_id: map_id,
map_user_settings: map_user_settings,
page_title: map_name,
user_permissions: user_permissions,
tracked_character_ids: tracked_character_ids,
@@ -334,7 +337,6 @@ defmodule WandererAppWeb.MapCoreEventHandler do
} = socket
) do
with {:ok, _} <- current_user |> WandererApp.Api.User.update_last_map(%{last_map_id: map_id}),
{:ok, map_user_settings} <- WandererApp.MapUserSettingsRepo.get(map_id, current_user.id),
{:ok, tracked_map_characters} <-
WandererApp.Maps.get_tracked_map_characters(map_id, current_user),
{:ok, characters_limit} <- map_id |> WandererApp.Map.get_characters_limit(),
@@ -414,7 +416,6 @@ defmodule WandererAppWeb.MapCoreEventHandler do
socket
|> map_start(%{
map_id: map_id,
map_user_settings: map_user_settings,
user_characters: user_character_eve_ids,
initial_data: initial_data,
events: events
@@ -437,7 +438,6 @@ defmodule WandererAppWeb.MapCoreEventHandler do
socket,
%{
map_id: map_id,
map_user_settings: map_user_settings,
user_characters: user_character_eve_ids,
initial_data: initial_data,
events: events
@@ -468,7 +468,6 @@ defmodule WandererAppWeb.MapCoreEventHandler do
socket
|> assign(
map_loaded?: true,
map_user_settings: map_user_settings,
user_characters: user_character_eve_ids,
has_tracked_characters?: has_tracked_characters?
)

View File

@@ -130,7 +130,7 @@ defmodule WandererAppWeb.MapSignaturesEventHandler do
if delete_connection_with_sigs && not is_nil(s.linked_system_id) do
map_id
|> WandererApp.Map.Server.delete_connection(%{
solar_system_source_id: solar_system_id |> String.to_integer(),
solar_system_source_id: system.solar_system_id,
solar_system_target_id: s.linked_system_id
})
end
@@ -180,6 +180,7 @@ defmodule WandererAppWeb.MapSignaturesEventHandler do
character_id: first_tracked_character.id,
user_id: current_user.id,
map_id: map_id,
solar_system_id: system.solar_system_id,
signatures: added_signatures_eve_ids
})
end
@@ -190,6 +191,7 @@ defmodule WandererAppWeb.MapSignaturesEventHandler do
character_id: first_tracked_character.id,
user_id: current_user.id,
map_id: map_id,
solar_system_id: system.solar_system_id,
signatures: removed_signatures_eve_ids
})
end

View File

@@ -21,57 +21,63 @@ defmodule WandererAppWeb.MapSystemsEventHandler do
|> MapEventHandler.push_map_event("remove_systems", solar_system_ids)
def handle_server_event(
%{
event: :maybe_select_system,
payload: %{
character_id: character_id,
solar_system_id: solar_system_id
}
},
%{assigns: %{current_user: current_user, map_id: map_id, map_user_settings: map_user_settings}} = socket
) do
%{
event: :maybe_select_system,
payload: %{
character_id: character_id,
solar_system_id: solar_system_id
}
},
%{
assigns: %{
current_user: current_user,
map_id: map_id,
map_user_settings: map_user_settings
}
} = socket
) do
is_user_character =
current_user.characters
|> Enum.map(& &1.id)
|> Enum.member?(character_id)
is_user_character =
current_user.characters
|> Enum.map(& &1.id)
|> Enum.member?(character_id)
is_select_on_spash =
map_user_settings
|> WandererApp.MapUserSettingsRepo.to_form_data!()
|> WandererApp.MapUserSettingsRepo.get_boolean_setting("select_on_spash")
is_select_on_spash =
map_user_settings
|> WandererApp.MapUserSettingsRepo.to_form_data!()
|> WandererApp.MapUserSettingsRepo.get_boolean_setting("select_on_spash")
is_followed =
case WandererApp.MapCharacterSettingsRepo.get_by_map(map_id, character_id) do
{:ok, setting} -> setting.followed == true
_ -> false
end
is_followed =
case WandererApp.MapCharacterSettingsRepo.get_by_map(map_id, character_id) do
{:ok, setting} -> setting.followed == true
_ -> false
end
must_select? = is_user_character && (is_select_on_spash || is_followed)
must_select? = is_user_character && (is_select_on_spash || is_followed)
if not must_select? do
if not must_select? do
socket
else
# Check if we already selected this exact system for this char:
last_selected =
WandererApp.Cache.lookup!(
"char:#{character_id}:map:#{map_id}:last_selected_system_id",
nil
)
if last_selected == solar_system_id do
# same system => skip
socket
else
# Check if we already selected this exact system for this char:
last_selected =
WandererApp.Cache.lookup!(
"char:#{character_id}:map:#{map_id}:last_selected_system_id",
nil
)
# new system => update cache + push event
WandererApp.Cache.put(
"char:#{character_id}:map:#{map_id}:last_selected_system_id",
solar_system_id
)
if last_selected == solar_system_id do
# same system => skip
socket
else
# new system => update cache + push event
WandererApp.Cache.put(
"char:#{character_id}:map:#{map_id}:last_selected_system_id",
solar_system_id
)
socket
|> MapEventHandler.push_map_event("select_system", solar_system_id)
end
socket
|> MapEventHandler.push_map_event("select_system", solar_system_id)
end
end
end
def handle_server_event(%{event: :kills_updated, payload: kills}, socket) do

View File

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

View File

@@ -0,0 +1,151 @@
%{
title: "Managing Upwell Structures & Timers with the Structures Widget",
author: "Wanderer Team",
cover_image_uri: "/images/news/01-20-structure-widget/cover.png",
tags: ~w(interface guide map structures),
description: "Learn how to track structure information using the Structures Widget."
}
---
### Introduction
Upwell structures like **Astrahus**, **Athanor**, and more are key strategic points in EVE Online. Staying informed about their statuses—whether theyre anchoring, powered, or reinforced—helps you plan defenses, coordinate attacks, and align with allies. Our **Structures Widget** simplifies the process by allowing you to:
- Copy structure information directly from the in-game Directional Scanner (`D-Scan`) and paste it into the widget.
- Keep track of **anchoring** or **reinforced** timers, including exact vulnerability windows.
- Share real-time data across the map with your corporation or alliance, ensuring everyone is on the same page.
In this guide, well explore how to enable the Structures Widget, manage structure data, and make use of the built-in API for remote structure updates.
---
### 1. Enabling the Structure Widget
![Enabling the Structures Widget](/images/news/01-20-structure-widget/enable-widget.png "Enable Structures Widget")
1. **Open the Map:**
2. **Locate the Widget Settings:** By default, the structure widget panel is not visible. Enable it by going to menu -> map settings -> widgets.
3. **Add the Structures Widget:** Click the checkbox for **Structures** from the list of available widgets.
> **Tip:** Rearrange your widgets by dragging them around the panel to suit your workflow.
---
### 2. Overview of the Structures Widget
![Structures Widget Overview](/images/news/01-20-structure-widget/cover.png "Structures Widget")
Once enabled, the **Structures Widget** appears in the map. It shows:
- **Structure Type** (Astrahus, Fortizar, etc.)
- **Structure Name** (auto-detected if you paste from D-Scan)
- **Owner** (Corporation ticker)
- **Status** (Powered, Anchoring, Low Power, Reinforced, etc.)
- **Timer** (Reinforced or anchoring end time)
You can **click** or **double-click** on an entry to edit details like the structures owner or add notes about the structures purpose or location.
---
### 3. Adding Structures via Copy & Paste
A fast way to add structure data is by copying from in-game D-Scan or show-info panels:
1. **In EVE Online:** Open the D-Scan window or structure context menu, select the relevant lines of text, and press **Ctrl + C**.
2. **In the Widget:** Focus on the Structures Widget, click in the widget area, and press **Ctrl + V** to paste or use the **blue** add structure info button.
3. The widget automatically parses the structure names and types. You can also add owners and notes manually.
This eliminates manual typing and reduces the chance of errors, especially useful when scanning multiple systems.
---
### 4. Tracking Reinforced Timers
When a structure is in a **Reinforced** or **Anchoring** state, we have a timer to note when it becomes vulnerable or completes anchoring:
- **Timer Field:** If the structures status is set to “Reinforced” or “Anchoring,” the widget enables a **Calendar** pop-up where you can set the _end time_.
Keep your fleet prepared by referencing this schedule. When the timer hits zero, the structure becomes vulnerable (or finishes anchoring).
---
### 5. Editing and Deleting Structures
1. **Single-click** a structure entry to select it.
2. Press **Delete** (or **Backspace**) to remove it entirely—useful when clearing out old data or removing outdated structures.
3. **Double-click** to open the **Edit Dialog**:
- Change **Name**, **Owner**, or **Status**.
- Update or remove **Reinforced** timers.
- Add or edit **Notes**.
Any changes made here are immediately visible to other map users.
---
### 6. API Integration for Automated Timers
Beyond the in-app widget, there is a dedicated API endpoint to fetch or update structure timers programmatically. This allows advanced users and third-party applications to seamlessly incorporate structure data.
**Example API Request/Response**:
```bash
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
"https://wanderer.yourdomain.space/api/map/structure-timers?slug=yourmap"
"data": [
{
"name": "Overlook Hotel",
"status": "Reinforced",
"notes": null,
"owner_id": null,
"solar_system_id": 31000515,
"solar_system_name": "J114942",
"character_eve_id": "2122839817",
"system_id": "4865aec4-b69d-4524-91d3-250b0556322b",
"end_time": "2025-01-22T23:42:03.000000Z",
"owner_name": null,
"owner_ticker": null,
"structure_type": "Astrahus",
"structure_type_id": "35832"
},
{
"name": "Some Structure",
"status": "Reinforced",
"notes": null,
"owner_id": null,
"solar_system_id": 3100229,
"solar_system_name": "somecustomname",
"character_eve_id": "some name",
"system_id": "ae779ed6-92b3-4349-899d-f1bdf299082f",
"end_time": "2025-01-16T03:04:00.000000Z",
"owner_name": null,
"owner_ticker": null,
"structure_type": "Athanor",
"structure_type_id": "35835"
}
]
```
With this API, you could, for example, build automated pings on Slack/Discord when timers are about to expire or display status updates on a custom web dashboard.
> **Note:** Ensure your API token (`Bearer YOUR_API_TOKEN`) matches the api key generated for you map.
---
### 7. Best Practices & Tips
- **Keep Data Fresh:** Update timers as soon as possible after a structure enters reinforcement. This keeps your corporation or alliance fully informed.
- **Use Notes Effectively:** Add details such as final reinforcement phases or relevant system intel (e.g., known hostiles, safe spots) to help allies plan more effectively.
---
## Conclusion
The **Structures Widget** is your central hub for monitoring, updating, and sharing information about Upwell structures across New Eden. From real-time timer tracking to simple copy-and-paste integration with D-Scan, this widget streamlines group operations and cuts down on manual data entry.
Whether youre a solo explorer managing a personal citadel network or a fleet commander overseeing multiple staging systems, the Structures Widget and its accompanying API ensure youll always have up-to-date intel on the structures that matter most.
Fly safe,
**The Wanderer Team**

View File

@@ -1,8 +1,9 @@
{
"21000325": true,
"21000326": true,
"21000327": true,
"21000328": true,
"21000329": true,
"21000330": true
}
"21000325": true,
"21000326": true,
"21000327": true,
"21000328": true,
"21000329": true,
"21000330": true,
"21000333": true
}

View File

@@ -222,7 +222,7 @@
{
"mass_regen": 500000000,
"dest": "hs",
"src": ["c3"],
"src": ["c3", "c4-shattered"],
"static": true,
"max_mass_per_jump": 300000000,
"lifetime": "24",