Compare commits

..

16 Commits

Author SHA1 Message Date
CI
27e9bab82a chore: release version v1.32.0
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 / 🏷 Create Release (push) Has been cancelled
2024-12-24 09:02:07 +00:00
Aleksei Chichenkov
edef860530 Merge pull request #85 from wanderer-industries/search-systems
Improve Search systems for Routes and Map (Manually add system)
2024-12-24 12:01:40 +03:00
achichenkov
032cb63411 Merge branch 'refs/heads/main' into search-systems 2024-12-24 11:43:02 +03:00
achichenkov
a1791ba578 fix(Map): Added ability to add new system to routes via routes widget 2024-12-24 11:42:46 +03:00
Dmitry Popov
3a69fd7786 chore(Map): Get rid of old add system modal 2024-12-23 11:13:49 +01:00
achichenkov
8a90723c2e fix(Map): Reworked add system to map 2024-12-23 13:06:15 +03:00
Dmitry Popov
af2fc342c7 chore(Map): Add search systems 2024-12-23 10:44:46 +01:00
Dmitry Popov
05ea2fcdbe chore(Map): Add default filtering to search systems 2024-12-22 22:59:28 +01:00
Dmitry Popov
6d4321fead chore(Map): Add static info to search systems 2024-12-22 17:16:42 +01:00
CI
3f6364c9ea chore: release version v1.31.0
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 / 🏷 Create Release (push) Has been cancelled
2024-12-20 15:05:20 +00:00
Dmitry Popov
0d11b12282 feat(Core): Show tracking for new users by default. Auto link characters to account fix. Add character loading indicators. 2024-12-20 16:04:31 +01:00
Dmitry Popov
0796bcf7d0 feat(Map): Add search & update manual adding systems API 2024-12-18 11:13:20 +01:00
Dmitry Popov
0b5bec142a feat(Map): Add search & update manual adding systems API 2024-12-18 11:09:52 +01:00
CI
a5020b58f2 chore: release version v1.30.2
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 / 🏷 Create Release (push) Has been cancelled
2024-12-17 16:31:33 +00:00
Aleksei Chichenkov
f039a74a8f Merge pull request #84 from wanderer-industries/fix-ship-size
fix(Map): Fixed problem with ship size change.
2024-12-17 19:30:56 +03:00
achichenkov
0e6bb7390b fix(Map): Fixed problem with ship size change. 2024-12-17 19:10:26 +03:00
30 changed files with 1552 additions and 404 deletions

View File

@@ -2,6 +2,41 @@
<!-- changelog -->
## [v1.32.0](https://github.com/wanderer-industries/wanderer/compare/v1.31.0...v1.32.0) (2024-12-24)
### Features:
* Map: Add search & update manual adding systems API
* Map: Add search & update manual adding systems API
### Bug Fixes:
* Map: Added ability to add new system to routes via routes widget
* Map: Reworked add system to map
## [v1.31.0](https://github.com/wanderer-industries/wanderer/compare/v1.30.2...v1.31.0) (2024-12-20)
### Features:
* Core: Show tracking for new users by default. Auto link characters to account fix. Add character loading indicators.
## [v1.30.2](https://github.com/wanderer-industries/wanderer/compare/v1.30.1...v1.30.2) (2024-12-17)
### Bug Fixes:
* Map: Fixed problem with ship size change.
## [v1.30.1](https://github.com/wanderer-industries/wanderer/compare/v1.30.0...v1.30.1) (2024-12-17)

View File

@@ -108,3 +108,7 @@
.p-dropdown-empty-message {
padding: 0.25rem 0.5rem;
}
.p-autocomplete .p-autocomplete-multiple-container .p-autocomplete-token {
margin-right: 0 !important;
}

View File

@@ -29,7 +29,7 @@ import {
useContextMenuConnectionHandlers,
useContextMenuRootHandlers,
} from './components';
import { OnMapSelectionChange } from './map.types';
import { OnMapAddSystemCallback, OnMapSelectionChange } from './map.types';
import { SESSION_KEY } from '@/hooks/Mapper/constants.ts';
import { SolarSystemConnection, SolarSystemRawType } from '@/hooks/Mapper/types';
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
@@ -92,6 +92,7 @@ interface MapCompProps {
onSelectionChange: OnMapSelectionChange;
onManualDelete(systems: string[]): void;
onConnectionInfoClick?(e: SolarSystemConnection): void;
onAddSystem?: OnMapAddSystemCallback;
onSelectionContextMenu?: NodeSelectionMouseHandler;
minimapClasses?: string;
isShowMinimap?: boolean;
@@ -116,6 +117,7 @@ const MapComp = ({
isThickConnections,
isShowBackgroundPattern,
isSoftBackground,
onAddSystem,
}: MapCompProps) => {
const { getNode } = useReactFlow();
const [nodes, , onNodesChange] = useNodesState<Node<SolarSystemRawType>>(initialNodes);
@@ -123,7 +125,7 @@ const MapComp = ({
useMapHandlers(refn, onSelectionChange);
useUpdateNodes(nodes);
const { handleRootContext, ...rootCtxProps } = useContextMenuRootHandlers();
const { handleRootContext, ...rootCtxProps } = useContextMenuRootHandlers({ onAddSystem });
const { handleConnectionContext, ...connectionCtxProps } = useContextMenuConnectionHandlers();
const { update } = useMapState();

View File

@@ -97,14 +97,16 @@ export const useContextMenuConnectionHandlers = () => {
},
});
outCommand({
type: OutCommand.updateConnectionMassStatus,
data: {
source: edge.source,
target: edge.target,
value: MassState.normal,
},
});
if (status === ShipSizeStatus.small) {
outCommand({
type: OutCommand.updateConnectionMassStatus,
data: {
source: edge.source,
target: edge.target,
value: MassState.normal,
},
});
}
}, []);
const onToggleMassSave = useCallback((locked: boolean) => {

View File

@@ -1,14 +1,16 @@
import { useReactFlow, XYPosition } from 'reactflow';
import React, { useRef, useState } from 'react';
import React, { useCallback, useRef, useState } from 'react';
import { ContextMenu } from 'primereact/contextmenu';
import { useMapState } from '../../MapProvider.tsx';
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
import { OnMapAddSystemCallback } from '@/hooks/Mapper/components/map/map.types.ts';
export const useContextMenuRootHandlers = () => {
type UseContextMenuRootHandlers = {
onAddSystem?: OnMapAddSystemCallback;
};
export const useContextMenuRootHandlers = ({ onAddSystem }: UseContextMenuRootHandlers = {}) => {
const rf = useReactFlow();
const contextMenuRef = useRef<ContextMenu | null>(null);
const { outCommand } = useMapState();
const [position, setPosition] = useState<XYPosition | null>(null);
const handleRootContext = (e: React.MouseEvent<HTMLDivElement>) => {
@@ -18,14 +20,17 @@ export const useContextMenuRootHandlers = () => {
contextMenuRef.current?.show(e);
};
const onAddSystem = () => {
outCommand({ type: OutCommand.manualAddSystem, data: { coordinates: position } });
};
const ref = useRef({ onAddSystem, position });
ref.current = { onAddSystem, position };
const onAddSystemCallback = useCallback(() => {
ref.current.onAddSystem?.({ coordinates: position });
}, [position]);
return {
handleRootContext,
contextMenuRef,
onAddSystem,
onAddSystem: onAddSystemCallback,
};
};

View File

@@ -1,5 +1,6 @@
import { SolarSystemRawType } from '@/hooks/Mapper/types/system';
import { SolarSystemConnection } from '@/hooks/Mapper/types';
import { XYPosition } from 'reactflow';
export type MapSolarSystemType = Omit<SolarSystemRawType, 'position'>;
@@ -7,3 +8,5 @@ export type OnMapSelectionChange = (event: {
systems: string[];
connections: Pick<SolarSystemConnection, 'source' | 'target'>[];
}) => void;
export type OnMapAddSystemCallback = (props: { coordinates: XYPosition | null }) => void;

View File

@@ -0,0 +1,10 @@
.SearchItem {
& > * {
font-size: 13px !important;
}
}
.SearchItemEffect {
font-weight: initial !important;
}

View File

@@ -0,0 +1,203 @@
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 classes from './AddSystemDialog.module.scss';
import clsx from 'clsx';
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace.ts';
import { sortWHClasses } from '@/hooks/Mapper/helpers';
export type SearchOnSubmitCallback = (item: SearchSystemItem) => void;
interface AddSystemDialogProps {
title?: string;
visible: boolean;
setVisible: (visible: boolean) => void;
onSubmit?: SearchOnSubmitCallback;
excludedSystems?: number[];
}
export const AddSystemDialog = ({
title = 'Add system',
visible,
setVisible,
onSubmit,
excludedSystems = [],
}: AddSystemDialogProps) => {
const {
outCommand,
data: { wormholesData },
} = useMapRootState();
const inputRef = useRef<any>();
const onShow = useCallback(() => {
inputRef.current?.focus();
}, []);
const [filteredItems, setFilteredItems] = useState<SearchSystemItem[]>([]);
const [selectedItem, setSelectedItem] = useState<SearchSystemItem[] | null>(null);
const searchItems = useCallback(
async (event: { query: string }) => {
if (event.query.length < 2) {
setFilteredItems([]);
return;
}
const query = event.query;
if (query.length === 0) {
setFilteredItems([]);
} else {
try {
const result = await outCommand({
type: OutCommand.searchSystems,
data: {
text: query,
},
});
let prepared = (result.systems as SearchSystemItem[]).sort((a, b) => {
const amatch = a.label.indexOf(query);
const bmatch = b.label.indexOf(query);
return amatch - bmatch;
});
if (excludedSystems) {
prepared = prepared.filter(x => !excludedSystems.includes(x.system_static_info.solar_system_id));
}
setFilteredItems(prepared);
} catch (error) {
console.error('Error fetching data:', error);
setFilteredItems([]);
}
}
},
[excludedSystems, outCommand],
);
const ref = useRef({ onSubmit, selectedItem });
ref.current = { onSubmit, selectedItem };
const handleSubmit = useCallback(() => {
const { onSubmit, selectedItem } = ref.current;
setFilteredItems([]);
setSelectedItem([]);
if (!selectedItem) {
setVisible(false);
return;
}
onSubmit?.(selectedItem[0]);
setVisible(false);
}, [setVisible]);
return (
<Dialog
header={title}
visible={visible}
draggable={false}
style={{ width: '520px' }}
onShow={onShow}
onHide={() => {
if (!visible) {
return;
}
setVisible(false);
}}
>
<div className="flex flex-col gap-3 px-1.5">
<div className="flex flex-col gap-2 py-3.5">
<div className="flex flex-col gap-1">
<IconField>
<AutoComplete
ref={inputRef}
multiple
showEmptyMessage
scrollHeight="300px"
value={selectedItem}
suggestions={filteredItems}
completeMethod={searchItems}
onChange={e => {
setSelectedItem(e.value.length < 2 ? e.value : [e.value[e.value.length - 1]]);
}}
emptyMessage="Not found any system..."
placeholder="Type here..."
field="label"
id="value"
className="w-full"
itemTemplate={(item: SearchSystemItem) => {
const { security, system_class, effect_power, effect_name, statics } = item.system_static_info;
const sortedStatics = sortWHClasses(wormholesData, statics);
const isWH = isWormholeSpace(system_class);
return (
<div className={clsx('flex gap-1.5', classes.SearchItem)}>
<SystemViewStandalone
security={security}
system_class={system_class}
solar_system_id={item.value}
class_title={item.class_title}
solar_system_name={item.label}
region_name={item.region_name}
/>
{effect_name && isWH && (
<WHEffectView
effectName={effect_name}
effectPower={effect_power}
className={classes.SearchItemEffect}
/>
)}
{isWH && (
<div className="flex gap-1 grow justify-between">
<div></div>
<div className="flex gap-1">
{sortedStatics.map(x => (
<WHClassView key={x} whClassName={x} />
))}
</div>
</div>
)}
</div>
);
}}
selectedItemTemplate={(item: SearchSystemItem) => (
<SystemViewStandalone
security={item.system_static_info.security}
system_class={item.system_static_info.system_class}
solar_system_id={item.value}
class_title={item.class_title}
solar_system_name={item.label}
region_name={item.region_name}
/>
)}
/>
</IconField>
<span className="text-[12px] text-stone-400 ml-1">*to search type at least 2 symbols.</span>
</div>
</div>
<div className="flex gap-2 justify-end">
<Button
onClick={handleSubmit}
outlined
disabled={!selectedItem || selectedItem.length !== 1}
size="small"
label="Submit"
/>
</div>
</div>
</Dialog>
);
};

View File

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

View File

@@ -21,6 +21,11 @@ import { RoutesProvider, useRouteProvider } from './RoutesProvider.tsx';
import { ContextMenuSystemInfo, useContextMenuSystemInfoHandlers } from '@/hooks/Mapper/components/contexts';
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth.ts';
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
import {
AddSystemDialog,
SearchOnSubmitCallback,
} from '@/hooks/Mapper/components/mapInterface/components/AddSystemDialog';
import { OutCommand } from '@/hooks/Mapper/types';
const sortByDist = (a: Route, b: Route) => {
const distA = a.has_connection ? a.systems?.length || 0 : Infinity;
@@ -163,6 +168,12 @@ export const RoutesWidgetContent = () => {
export const RoutesWidgetComp = () => {
const [routeSettingsVisible, setRouteSettingsVisible] = useState(false);
const { data, update } = useRouteProvider();
const {
data: { hubs = [] },
outCommand,
} = useMapRootState();
const preparedHubs = useMemo(() => hubs.map(x => parseInt(x)), [hubs]);
const isSecure = data.path_type === 'secure';
const handleSecureChange = useCallback(() => {
@@ -174,6 +185,23 @@ export const RoutesWidgetComp = () => {
const ref = useRef<HTMLDivElement>(null);
const compact = useMaxWidth(ref, 155);
const [openAddSystem, setOpenAddSystem] = useState<boolean>(false);
const onAddSystem = useCallback(() => setOpenAddSystem(true), []);
const handleSubmitAddSystem: SearchOnSubmitCallback = useCallback(
async item => {
if (preparedHubs.includes(item.value)) {
return;
}
await outCommand({
type: OutCommand.addHub,
data: { system_id: item.value },
});
},
[hubs, outCommand],
);
return (
<Widget
@@ -181,6 +209,14 @@ export const RoutesWidgetComp = () => {
<div className="flex justify-between items-center text-xs w-full" ref={ref}>
<span className="select-none">Routes</span>
<LayoutEventBlocker className="flex items-center gap-2">
<WdImgButton
className={PrimeIcons.PLUS_CIRCLE}
onClick={onAddSystem}
tooltip={{
content: 'Click here to add new system to routes',
}}
/>
<WdTooltipWrapper content="Show shortest route">
<WdCheckbox
size="xs"
@@ -191,13 +227,26 @@ export const RoutesWidgetComp = () => {
classNameLabel={clsx('text-red-400')}
/>
</WdTooltipWrapper>
<WdImgButton className={PrimeIcons.SLIDERS_H} onClick={() => setRouteSettingsVisible(true)} />
<WdImgButton
className={PrimeIcons.SLIDERS_H}
onClick={() => setRouteSettingsVisible(true)}
tooltip={{
content: 'Click here to open Routes settings',
}}
/>
</LayoutEventBlocker>
</div>
}
>
<RoutesWidgetContent />
<RoutesSettingsDialog visible={routeSettingsVisible} setVisible={setRouteSettingsVisible} />
<AddSystemDialog
title="Add system to routes"
visible={openAddSystem}
setVisible={() => setOpenAddSystem(false)}
onSubmit={handleSubmitAddSystem}
/>
</Widget>
);
};

View File

@@ -2,7 +2,7 @@ import { Map } from '@/hooks/Mapper/components/map/Map.tsx';
import { useCallback, useRef, useState } from 'react';
import { OutCommand, OutCommandHandler, SolarSystemConnection } from '@/hooks/Mapper/types';
import { MapRootData, useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts';
import { OnMapAddSystemCallback, OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts';
import isEqual from 'lodash.isequal';
import { ContextMenuSystem, useContextMenuSystemHandlers } from '@/hooks/Mapper/components/contexts';
import {
@@ -14,14 +14,18 @@ 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 { Node } from 'reactflow';
import { Node, XYPosition } from 'reactflow';
import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts';
import { useMapEventListener } from '@/hooks/Mapper/events';
import { emitMapEvent, useMapEventListener } from '@/hooks/Mapper/events';
import { STORED_INTERFACE_DEFAULT_VALUES } from '@/hooks/Mapper/mapRootProvider/MapRootProvider';
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
import { useCommonMapEventProcessor } from '@/hooks/Mapper/components/mapWrapper/hooks/useCommonMapEventProcessor.ts';
import {
AddSystemDialog,
SearchOnSubmitCallback,
} from '@/hooks/Mapper/components/mapInterface/components/AddSystemDialog';
// TODO: INFO - this component needs for abstract work with Map instance
export const MapWrapper = () => {
@@ -47,6 +51,7 @@ export const MapWrapper = () => {
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 });
@@ -123,6 +128,28 @@ export const MapWrapper = () => {
}
}, []);
const onAddSystem: OnMapAddSystemCallback = useCallback(({ coordinates }) => {
setOpenAddSystem(coordinates);
}, []);
const handleSubmitAddSystem: SearchOnSubmitCallback = useCallback(
async item => {
if (ref.current.systems.some(x => x.system_static_info.solar_system_id === item.value)) {
emitMapEvent({
name: Commands.centerSystem,
data: item.value.toString(),
});
return;
}
await outCommand({
type: OutCommand.manualAddSystem,
data: { coordinates: openAddSystem, solar_system_id: item.value },
});
},
[openAddSystem, outCommand],
);
return (
<>
<Map
@@ -139,6 +166,7 @@ export const MapWrapper = () => {
isThickConnections={isThickConnections}
isShowBackgroundPattern={isShowBackgroundPattern}
isSoftBackground={isSoftBackground}
onAddSystem={onAddSystem}
/>
{openSettings != null && (
@@ -153,6 +181,12 @@ export const MapWrapper = () => {
<SystemLinkSignatureDialog data={openLinkSignatures} setVisible={() => setOpenLinkSignatures(null)} />
)}
<AddSystemDialog
visible={!!openAddSystem}
setVisible={() => setOpenAddSystem(null)}
onSubmit={handleSubmitAddSystem}
/>
<Connections selectedConnection={selectedConnection} onHide={() => setSelectedConnection(null)} />
<ContextMenuSystem

View File

@@ -31,12 +31,15 @@ const prepareEffects = (effects: Record<string, EffectRaw>, effectName: string,
return out;
};
let counter = 0;
export interface WHEffectViewProps {
effectName: string;
effectPower: number;
className?: string;
}
export const WHEffectView = ({ effectName, effectPower }: WHEffectViewProps) => {
export const WHEffectView = ({ effectName, effectPower, className }: WHEffectViewProps) => {
const {
data: { effects },
} = useMapRootState();
@@ -49,7 +52,7 @@ export const WHEffectView = ({ effectName, effectPower }: WHEffectViewProps) =>
[effectName, effectPower, effects],
);
const targetClass = `wh-effect-name${effectInfo.id}`;
const targetClass = useMemo(() => `wh-effect-name${effectInfo.id}-${counter++}`, []);
return (
<div className={classes.WHEffectViewRoot}>
@@ -84,7 +87,7 @@ export const WHEffectView = ({ effectName, effectPower }: WHEffectViewProps) =>
</div>
</FixedTooltip>
<div className={clsx('font-bold select-none cursor-help w-min-content', effectClass, targetClass)}>
<div className={clsx('font-bold select-none cursor-help w-min-content', effectClass, targetClass, className)}>
{effectName}
</div>
</div>

View File

@@ -153,6 +153,7 @@ export enum OutCommand {
getUserSettings = 'get_user_settings',
updateUserSettings = 'update_user_settings',
unlinkSignature = 'unlink_signature',
searchSystems = 'search_systems',
}
export type OutCommandHandler = <T = any>(event: { type: OutCommand; data: any }) => Promise<T>;

View File

@@ -120,3 +120,13 @@ export type SolarSystemRawType = {
system_static_info: SolarSystemStaticInfoRaw;
system_signatures: SystemSignature[];
};
export type SearchSystemItem = {
class_title: string;
constellation_name: string;
label: string;
region_name: string;
system_static_info: SolarSystemStaticInfoRaw;
value: number;
};

View File

@@ -8,6 +8,7 @@ import CopyToClipboard from './copyToClipboard';
import DownloadJson from './downloadJson';
import NewVersionUpdate from './newVersionUpdate';
import MapAction from './maps/mapAction';
import ShowCharactersAddAlert from './showCharactersAddAlert';
export default {
DownloadJson,
@@ -20,4 +21,5 @@ export default {
Ping,
CopyToClipboard,
NewVersionUpdate,
ShowCharactersAddAlert,
};

View File

@@ -0,0 +1,11 @@
export default {
mounted() {
this.pushEvent('restore_show_characters_add_alert', {
value: localStorage.getItem('wanderer:hide_characters_add_alert') !== 'true',
});
document.getElementById('characters-add-alert-hide')?.addEventListener('click', e => {
localStorage.setItem('wanderer:hide_characters_add_alert', 'true');
});
},
};

View File

@@ -359,33 +359,30 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
def can_add_location(:none, _solar_system_id), do: false
def can_add_location(scope, solar_system_id) do
system_static_info =
case WandererApp.CachedInfo.get_system_static_info(solar_system_id) do
{:ok, system_static_info} when not is_nil(system_static_info) ->
system_static_info
_ ->
%{system_class: nil}
end
{:ok, system_static_info} = get_system_static_info(solar_system_id)
case scope do
:wormholes ->
not (@prohibited_system_classes |> Enum.member?(system_static_info.system_class)) and
not is_prohibited_system_class?(system_static_info.system_class) and
not (@prohibited_systems |> Enum.member?(solar_system_id)) and
@wh_space |> Enum.member?(system_static_info.system_class)
:stargates ->
not (@prohibited_system_classes |> Enum.member?(system_static_info.system_class)) and
not is_prohibited_system_class?(system_static_info.system_class) and
@known_space |> Enum.member?(system_static_info.system_class)
:all ->
not (@prohibited_system_classes |> Enum.member?(system_static_info.system_class))
not is_prohibited_system_class?(system_static_info.system_class)
_ ->
false
end
end
def is_prohibited_system_class?(system_class) do
@prohibited_system_classes |> Enum.member?(system_class)
end
def is_connection_exist(map_id, from_solar_system_id, to_solar_system_id),
do:
not is_nil(
@@ -414,24 +411,21 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
current_system_id: to_solar_system_id
})
system_static_info =
case WandererApp.CachedInfo.get_system_static_info(to_solar_system_id) do
{:ok, system_static_info} when not is_nil(system_static_info) ->
system_static_info
_ ->
%{system_class: nil}
end
{:ok, from_system_static_info} = get_system_static_info(from_solar_system_id)
{:ok, to_system_static_info} = get_system_static_info(to_solar_system_id)
case scope do
:wormholes ->
not (@prohibited_system_classes |> Enum.member?(system_static_info.system_class)) and
not is_prohibited_system_class?(from_system_static_info.system_class) and
not is_prohibited_system_class?(to_system_static_info.system_class) and
not (@prohibited_systems |> Enum.member?(from_solar_system_id)) and
not (@prohibited_systems |> Enum.member?(to_solar_system_id)) and
known_jumps |> Enum.empty?() and to_solar_system_id != @jita and
from_solar_system_id != @jita
:stargates ->
not (@prohibited_system_classes |> Enum.member?(system_static_info.system_class)) and
not is_prohibited_system_class?(from_system_static_info.system_class) and
not is_prohibited_system_class?(to_system_static_info.system_class) and
not (known_jumps |> Enum.empty?())
end
end
@@ -447,6 +441,16 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
end
end
defp get_system_static_info(solar_system_id) do
case WandererApp.CachedInfo.get_system_static_info(solar_system_id) do
{:ok, system_static_info} when not is_nil(system_static_info) ->
{:ok, system_static_info}
_ ->
{:ok, %{system_class: nil}}
end
end
defp maybe_remove_connection(map_id, location, old_location)
when not is_nil(location) and not is_nil(old_location) and
location.solar_system_id != old_location.solar_system_id do

View File

@@ -93,13 +93,9 @@ defmodule WandererAppWeb.AuthController do
|> redirect(to: ~p"/")
end
def maybe_update_character_user_id(character, user_id) do
case character.user_id do
nil ->
WandererApp.Api.Character.assign_user!(character, %{user_id: user_id})
_ ->
Logger.debug("character already has user_id")
end
def maybe_update_character_user_id(character, user_id) when not is_nil(user_id) do
WandererApp.Api.Character.assign_user!(character, %{user_id: user_id})
end
def maybe_update_character_user_id(_character, _user_id), do: :ok
end

View File

@@ -28,6 +28,7 @@ defmodule WandererAppWeb.CharactersLive do
{:ok,
socket
|> assign(
show_characters_add_alert: true,
mode: :blocks,
wallet_tracking_enabled?: WandererApp.Env.wallet_tracking_enabled?(),
characters: characters |> Enum.sort_by(& &1.name, :asc) |> Enum.map(&map_ui_character/1),
@@ -45,6 +46,13 @@ defmodule WandererAppWeb.CharactersLive do
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
end
@impl true
def handle_event("restore_show_characters_add_alert", %{"value" => value}, socket) do
{:noreply,
socket
|> assign(show_characters_add_alert: value)}
end
@impl true
def handle_event("authorize", form, socket) do
track_wallet = form |> Map.get("track_wallet", false)

View File

@@ -29,9 +29,45 @@
id="characters-list"
class="w-full h-full col-span-2 lg:col-span-1 p-4 pl-20 pb-20 overflow-auto"
>
<div
:if={@show_characters_add_alert}
role="alert"
class="alert"
id="characters-add-alert"
phx-hook="ShowCharactersAddAlert"
phx-ignore
data-key="show_characters_add_alert"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="h-6 w-6 shrink-0 stroke-current"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
>
</path>
</svg>
<span>
All added characters will be automatically linked to your user account. You can't have same characters linked to several accounts.
</span>
<div>
<button
class="btn btn-sm"
id="characters-add-alert-hide"
phx-click={JS.toggle_class("hidden", to: "#characters-add-alert")}
>
Hide
</button>
</div>
</div>
<div
:if={@mode == :blocks}
class="gap-4 grid grid-cols-1 lg:grid-cols-5 md:grid-cols-3 sm:grid-cols-2 "
class="gap-4 grid grid-cols-1 lg:grid-cols-5 md:grid-cols-3 sm:grid-cols-2 mt-4"
>
<.link patch={~p"/characters/authorize"}>
<div class="card card-side rounded-none h-full items-center hover:text-white bg-gradient-to-l from-stone-950 to-stone-900 transform transition duration-500">
@@ -51,9 +87,24 @@
<div class="absolute left-0 bottom-0 w-full h-30 bg-opacity-60 bg-gray-900">
<h2 class="absolute w-full flex justify-between px-4 left-0 top-10 text-xs">
Corporation:
<span
:if={
is_nil(
character
|> get(
path(:corporation_name),
nil
)
)
}
class="loading loading-dots loading-xs"
/>
<span>
<%= character
|> get(path(:corporation_name), "-") %>
|> get(
path(:corporation_name),
""
) %>
</span>
</h2>
<h2 class="absolute w-full flex justify-between px-4 left-0 top-16 text-xs">
@@ -65,9 +116,21 @@
</h2>
<h2 class="absolute left-0 bottom-12 px-4 text-xs w-full flex justify-between">
Location:
<span
:if={
is_nil(
character
|> get(
path(:location / :solar_system_info / :solar_system_name, :map),
nil
)
)
}
class="loading loading-dots loading-xs"
/>
<span>
<%= character
|> get(path(:location / :solar_system_info / :solar_system_name, :map), "-") %>
|> get(path(:location / :solar_system_info / :solar_system_name, :map), "") %>
</span>
</h2>
<h2
@@ -126,7 +189,7 @@
</div>
</div>
<div :if={@mode == :table} class="flex flex-col gap-4">
<div :if={@mode == :table} class="flex flex-col gap-4 mt-4">
<.link patch={~p"/characters/authorize"}>
<button
type="button"

View File

@@ -66,35 +66,9 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
def handle_ui_event(
"add_character",
_,
%{
assigns: %{
current_user: current_user,
map_id: map_id,
user_permissions: %{track_character: true}
}
} = socket
) do
{:noreply,
socket
|> assign(show_tracking?: true)
|> assign_async(:characters, fn ->
{:ok, map} =
map_id
|> WandererApp.MapRepo.get([:acls])
{:ok, character_settings} =
case WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id) do
{:ok, settings} -> {:ok, settings}
_ -> {:ok, []}
end
map
|> WandererApp.Maps.load_characters(
character_settings,
current_user.id
)
end)}
end
socket
),
do: {:noreply, socket |> add_character()}
def handle_ui_event(
"add_character",
@@ -222,6 +196,38 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
def handle_ui_event(event, body, socket),
do: MapCoreEventHandler.handle_ui_event(event, body, socket)
def add_character(
%{
assigns: %{
current_user: current_user,
map_id: map_id,
user_permissions: %{track_character: true}
}
} = socket
),
do:
socket
|> assign(show_tracking?: true)
|> assign_async(:characters, fn ->
{:ok, map} =
map_id
|> WandererApp.MapRepo.get([:acls])
{:ok, character_settings} =
case WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id) do
{:ok, settings} -> {:ok, settings}
_ -> {:ok, []}
end
map
|> WandererApp.Maps.load_characters(
character_settings,
current_user.id
)
end)
def add_character(socket), do: socket
def has_tracked_characters?([]), do: false
def has_tracked_characters?(_user_characters), do: true

View File

@@ -3,7 +3,7 @@ defmodule WandererAppWeb.MapCoreEventHandler do
use Phoenix.Component
require Logger
alias WandererAppWeb.{MapEventHandler, MapCharactersEventHandler}
alias WandererAppWeb.{MapEventHandler, MapCharactersEventHandler, MapSystemsEventHandler}
def handle_server_event(:update_permissions, socket) do
DebounceAndThrottle.Debounce.apply(
@@ -147,7 +147,7 @@ defmodule WandererAppWeb.MapCoreEventHandler do
options =
WandererApp.Api.MapSolarSystem.find_by_name!(%{name: text})
|> Enum.take(100)
|> Enum.map(&map_system/1)
|> Enum.map(&MapSystemsEventHandler.map_system/1)
send_update(LiveSelect.Component, options: options, id: id)
@@ -221,8 +221,9 @@ defmodule WandererAppWeb.MapCoreEventHandler do
socket
|> put_flash(
:error,
"You should enable tracking for at least one character."
)}
"You should enable tracking for at least one character!"
)
|> MapCharactersEventHandler.add_character()}
def handle_ui_event(event, body, socket) do
Logger.warning(fn -> "unhandled map ui event: #{event} #{inspect(body)}" end)
@@ -448,23 +449,35 @@ defmodule WandererAppWeb.MapCoreEventHandler do
|> filter_map_characters(user_character_eve_ids, user_permissions, options)
|> Enum.map(&MapCharactersEventHandler.map_ui_character/1)
socket
|> assign(
map_loaded?: true,
map_user_settings: map_user_settings,
user_characters: user_character_eve_ids,
has_tracked_characters?:
MapCharactersEventHandler.has_tracked_characters?(user_character_eve_ids)
)
|> MapEventHandler.push_map_event(
"init",
initial_data
|> Map.put(:characters, map_characters)
)
|> push_event("js-exec", %{
to: "#map-loader",
attr: "data-loaded"
})
has_tracked_characters? =
MapCharactersEventHandler.has_tracked_characters?(user_character_eve_ids)
socket =
socket
|> assign(
map_loaded?: true,
map_user_settings: map_user_settings,
user_characters: user_character_eve_ids,
has_tracked_characters?: has_tracked_characters?
)
|> MapEventHandler.push_map_event(
"init",
initial_data
|> Map.put(:characters, map_characters)
)
|> push_event("js-exec", %{
to: "#map-loader",
attr: "data-loaded"
})
case not has_tracked_characters? && user_permissions.track_character do
true ->
socket
|> MapCharactersEventHandler.add_character()
_ ->
socket
end
end
defp handle_map_start_events(socket, map_id, events) do
@@ -487,10 +500,6 @@ defmodule WandererAppWeb.MapCoreEventHandler do
:empty_tracked_characters ->
socket
|> put_flash(
:info,
"You should enable tracking for at least one character to work with map."
)
:map_character_limit ->
socket
@@ -555,21 +564,4 @@ defmodule WandererAppWeb.MapCoreEventHandler do
user_character_eve_ids |> Enum.member?(character.eve_id)
end)
end
defp map_system(
%{
solar_system_name: solar_system_name,
constellation_name: constellation_name,
region_name: region_name,
solar_system_id: solar_system_id,
class_title: class_title
} = _system
),
do: %{
label: solar_system_name,
value: solar_system_id,
constellation_name: constellation_name,
region_name: region_name,
class_title: class_title
}
end

View File

@@ -65,55 +65,32 @@ defmodule WandererAppWeb.MapSystemsEventHandler do
do: MapCoreEventHandler.handle_server_event(event, socket)
def handle_ui_event(
"add_system",
%{"system_id" => [solar_system_id]} = _event,
"manual_add_system",
%{"solar_system_id" => solar_system_id, "coordinates" => coordinates} = _event,
%{
assigns:
%{
map_id: map_id,
map_slug: map_slug,
current_user: current_user,
tracked_character_ids: tracked_character_ids,
user_permissions: %{add_system: true}
} = assigns
} = socket
)
when is_binary(solar_system_id) and solar_system_id != "" do
coordinates = Map.get(assigns, :coordinates)
assigns: %{
current_user: current_user,
has_tracked_characters?: true,
map_id: map_id,
tracked_character_ids: tracked_character_ids,
user_permissions: %{add_system: true}
}
} =
socket
) do
WandererApp.Map.Server.add_system(
map_id,
%{
solar_system_id: solar_system_id |> String.to_integer(),
solar_system_id: solar_system_id,
coordinates: coordinates
},
current_user.id,
tracked_character_ids |> List.first()
)
{:noreply,
socket
|> push_patch(to: ~p"/#{map_slug}")}
{:noreply, socket}
end
def handle_ui_event(
"manual_add_system",
%{"coordinates" => coordinates} = _event,
%{
assigns: %{
has_tracked_characters?: true,
map_slug: map_slug,
user_permissions: %{add_system: true}
}
} =
socket
),
do:
{:noreply,
socket
|> assign(coordinates: coordinates)
|> push_patch(to: ~p"/#{map_slug}/add-system")}
def handle_ui_event(
"add_hub",
%{"system_id" => solar_system_id} = _event,
@@ -280,6 +257,25 @@ defmodule WandererAppWeb.MapSystemsEventHandler do
{:reply, %{system_static_infos: system_static_infos}, socket}
end
def handle_ui_event(
"search_systems",
%{"text" => text} = _event,
socket
) do
systems =
WandererApp.Api.MapSolarSystem.find_by_name!(%{name: text})
|> Enum.take(100)
|> Enum.map(&map_system/1)
|> Enum.filter(fn system ->
not is_nil(system) && not is_nil(system.system_static_info) &&
not WandererApp.Map.Server.ConnectionsImpl.is_prohibited_system_class?(
system.system_static_info.system_class
)
end)
{:reply, %{systems: systems}, socket}
end
def handle_ui_event(
"delete_systems",
solar_system_ids,
@@ -307,6 +303,27 @@ defmodule WandererAppWeb.MapSystemsEventHandler do
def handle_ui_event(event, body, socket),
do: MapCoreEventHandler.handle_ui_event(event, body, socket)
def map_system(
%{
solar_system_name: solar_system_name,
constellation_name: constellation_name,
region_name: region_name,
solar_system_id: solar_system_id,
class_title: class_title
} = _system
) do
system_static_info = MapEventHandler.get_system_static_info(solar_system_id)
%{
label: solar_system_name,
value: solar_system_id,
constellation_name: constellation_name,
region_name: region_name,
class_title: class_title,
system_static_info: system_static_info
}
end
defp can_update_system?(:locked, %{lock_system: false} = _user_permissions), do: false
defp can_update_system?(_key, _user_permissions), do: true

View File

@@ -38,10 +38,10 @@ defmodule WandererAppWeb.MapEventHandler do
@map_system_ui_events [
"add_hub",
"delete_hub",
"add_system",
"delete_systems",
"manual_add_system",
"get_system_static_infos",
"manual_add_system",
"search_systems",
"update_system_position",
"update_system_positions",
"update_system_name",

View File

@@ -6,7 +6,7 @@ defmodule WandererAppWeb.MapLive do
@impl true
def mount(%{"slug" => map_slug} = _params, _session, socket) when is_connected?(socket) do
Process.send_after(self(), %{event: :load_map}, Enum.random(10..500))
Process.send_after(self(), %{event: :load_map}, Enum.random(10..800))
{:ok,
socket
@@ -76,13 +76,15 @@ defmodule WandererAppWeb.MapLive do
def handle_info(:not_all_characters_tracked, %{assigns: %{map_slug: map_slug}} = socket),
do:
{:noreply,
socket
|> put_flash(
:error,
"You should enable tracking for all characters that have access to this map first!"
)
|> push_navigate(to: ~p"/tracking/#{map_slug}")}
WandererAppWeb.MapEventHandler.handle_ui_event(
%{event: "add_character"},
nil,
socket
|> put_flash(
:error,
"You should enable tracking for all characters that have access to this map first!"
)
)
@impl true
def handle_info(info, socket),
@@ -100,13 +102,6 @@ defmodule WandererAppWeb.MapLive do
|> assign(:active_page, :map)
end
defp apply_action(socket, :add_system, _params) do
socket
|> assign(:active_page, :map)
|> assign(:page_title, "Add System")
|> assign(:add_system_form, to_form(%{"system_id" => nil}))
end
def character_item(assigns) do
~H"""
<div class="flex items-center gap-3">

View File

@@ -31,41 +31,6 @@
</.link>
</div>
<.modal
:if={@live_action in [:add_system] && not is_nil(assigns |> Map.get(:map_slug)) && @map_loaded?}
id="add-system-modal"
class="!w-[400px]"
title="Add System"
show
on_cancel={JS.patch(~p"/#{@map_slug}")}
>
<.form :let={f} for={@add_system_form} phx-submit="add_system">
<.live_select
label="Search system"
field={f[:system_id]}
update_min_len={2}
available_option_class="w-full text-sm"
debounce={200}
mode={:tags}
>
<:option :let={option}>
<div class="gap-1 w-full flex flex-align-center p-autocomplete-item text-sm">
<div class="eve-wh-type-color-c1 text-gray-400 w-8"><%= option.class_title %></div>
<div class="text-white w-16"><%= option.label %></div>
<div class="text-gray-600 w-20"><%= option.constellation_name %></div>
<div class="text-gray-600"><%= option.region_name %></div>
</div>
</:option>
</.live_select>
<div class="mt-2 bg-neutral text-neutral-content rounded-md p-1 text-xs w-full">
* Start search system. You should type at least 2 symbols.
</div>
<div class="modal-action mt-0">
<.button class="mt-2" type="submit">Add</.button>
</div>
</.form>
</.modal>
<.modal
:if={assigns |> Map.get(:show_activity?, false)}
id="map-activity-modal"
@@ -92,12 +57,12 @@
<.modal
:if={assigns |> Map.get(:show_tracking?, false)}
id="map-tracking-modal"
title="Characters tracking"
title="Track Characters"
show
on_cancel={JS.push("hide_tracking")}
>
<.async_result :let={characters} assign={@characters}>
<:loading><span class="loading loading-dots loading-xs" />.</:loading>
<:loading><span class="loading loading-dots loading-xs" /></:loading>
<:failed :let={reason}><%= reason %></:failed>
<.table
@@ -106,7 +71,7 @@
class="h-[400px] !overflow-y-auto"
rows={characters}
>
<:col :let={character} label="Tracked">
<:col :let={character} label="Track">
<label class="flex items-center gap-3">
<input
type="checkbox"

View File

@@ -197,7 +197,6 @@ defmodule WandererAppWeb.Router do
live("/profile/deposit", ProfileLive, :deposit)
live("/profile/subscribe", ProfileLive, :subscribe)
live("/:slug/audit", MapAuditLive, :index)
live("/:slug/add-system", MapLive, :add_system)
live("/:slug", MapLive, :index)
end
end

View File

@@ -2,7 +2,7 @@ defmodule WandererApp.MixProject do
use Mix.Project
@source_url "https://github.com/wanderer-industries/wanderer"
@version "1.30.1"
@version "1.32.0"
def project do
[

View File

@@ -755,11 +755,11 @@ groupID,categoryID,groupName,iconID,useBasePrice,anchored,anchorable,fittableNon
944,9,Capital Industrial Ship Blueprint,None,1,0,0,0,1
945,9,Industrial Command Ship Blueprint,None,1,0,0,0,1
952,11,Mission Container,0,0,1,0,0,0
954,32,Defensive Subsystem,None,0,0,0,0,1
954,32,Defensive Subsystem,3631,0,0,0,0,1
955,17,Depricated Subsystems,None,0,0,0,0,0
956,32,Offensive Subsystem,None,0,0,0,0,1
957,32,Propulsion Subsystem,None,0,0,0,0,1
958,32,Core Subsystem,None,0,0,0,0,1
956,32,Offensive Subsystem,3641,0,0,0,0,1
957,32,Propulsion Subsystem,3646,0,0,0,0,1
958,32,Core Subsystem,3636,0,0,0,0,1
959,11,Deadspace Sleeper Sleepless Sentinel,0,0,0,0,0,0
960,11,Deadspace Sleeper Awakened Sentinel,0,0,0,0,0,0
961,11,Deadspace Sleeper Emergent Sentinel,0,0,0,0,0,0
@@ -1529,6 +1529,8 @@ groupID,categoryID,groupName,iconID,useBasePrice,anchored,anchorable,fittableNon
4811,9,Mercenary Den Blueprint,None,1,0,0,0,1
4820,9,Mutaplasmid Blueprint,None,1,0,0,0,1
4821,17,Atavum,None,1,0,0,0,1
4824,17,Infomorph Systems,None,1,0,0,0,1
4825,2,Local Beacon,None,0,1,0,0,0
350858,350001,Infantry Weapons,None,1,0,0,0,0
351064,350001,Infantry Dropsuits,None,1,0,0,0,0
351121,350001,Infantry Modules,None,1,0,0,0,0
1 groupID categoryID groupName iconID useBasePrice anchored anchorable fittableNonSingleton published
755 944 9 Capital Industrial Ship Blueprint None 1 0 0 0 1
756 945 9 Industrial Command Ship Blueprint None 1 0 0 0 1
757 952 11 Mission Container 0 0 1 0 0 0
758 954 32 Defensive Subsystem None 3631 0 0 0 0 1
759 955 17 Depricated Subsystems None 0 0 0 0 0
760 956 32 Offensive Subsystem None 3641 0 0 0 0 1
761 957 32 Propulsion Subsystem None 3646 0 0 0 0 1
762 958 32 Core Subsystem None 3636 0 0 0 0 1
763 959 11 Deadspace Sleeper Sleepless Sentinel 0 0 0 0 0 0
764 960 11 Deadspace Sleeper Awakened Sentinel 0 0 0 0 0 0
765 961 11 Deadspace Sleeper Emergent Sentinel 0 0 0 0 0 0
1529 4811 9 Mercenary Den Blueprint None 1 0 0 0 1
1530 4820 9 Mutaplasmid Blueprint None 1 0 0 0 1
1531 4821 17 Atavum None 1 0 0 0 1
1532 4824 17 Infomorph Systems None 1 0 0 0 1
1533 4825 2 Local Beacon None 0 1 0 0 0
1534 350858 350001 Infantry Weapons None 1 0 0 0 0
1535 351064 350001 Infantry Dropsuits None 1 0 0 0 0
1536 351121 350001 Infantry Modules None 1 0 0 0 0

File diff suppressed because it is too large Load Diff