mirror of
https://github.com/wanderer-industries/wanderer
synced 2025-11-29 20:43:23 +00:00
Compare commits
88 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ddcab3537 | ||
|
|
040b46c345 | ||
|
|
cd11ab6775 | ||
|
|
e02caf341d | ||
|
|
3c04caa67c | ||
|
|
e76b564cbf | ||
|
|
5b2de88c3d | ||
|
|
82080b232f | ||
|
|
666bc7af43 | ||
|
|
dc077d5a5b | ||
|
|
29c840c64a | ||
|
|
65e0f89f33 | ||
|
|
d3b9b36332 | ||
|
|
90bbf29ea1 | ||
|
|
57f73684e8 | ||
|
|
7833cdebb2 | ||
|
|
67a5ae2985 | ||
|
|
f58c52d26b | ||
|
|
41e7739461 | ||
|
|
332152b677 | ||
|
|
85b49fe1f0 | ||
|
|
e7924532be | ||
|
|
475d950ad6 | ||
|
|
e6cfb29c6f | ||
|
|
dee6e86db1 | ||
|
|
72f088331f | ||
|
|
bbf536d10e | ||
|
|
149ac98297 | ||
|
|
b90a2910c9 | ||
|
|
c4da8a3a8d | ||
|
|
3ca75583d2 | ||
|
|
5f4607ae6f | ||
|
|
d880c6873f | ||
|
|
937649b2ed | ||
|
|
78e912c886 | ||
|
|
696c7d2cd1 | ||
|
|
49c0cb026b | ||
|
|
7091341b4b | ||
|
|
8795ce6af3 | ||
|
|
239b34d15a | ||
|
|
729a5ad1a9 | ||
|
|
8febc2476b | ||
|
|
84b1317927 | ||
|
|
bfb504e5db | ||
|
|
9975deacfb | ||
|
|
f77f071003 | ||
|
|
4a8d55e83d | ||
|
|
9a99f40e2a | ||
|
|
428fa8035c | ||
|
|
745f3dee17 | ||
|
|
9907cc1875 | ||
|
|
130cd780a2 | ||
|
|
a808e5d1a5 | ||
|
|
b926117e26 | ||
|
|
fdf238accf | ||
|
|
4e1c27e8a3 | ||
|
|
a3e51a0ac5 | ||
|
|
d27bb0d54f | ||
|
|
f6a750f06b | ||
|
|
c4e2f63e69 | ||
|
|
675ffc8f42 | ||
|
|
cdc4221175 | ||
|
|
843f3363fd | ||
|
|
17653a6374 | ||
|
|
7d860533a0 | ||
|
|
0c751b3ced | ||
|
|
80a522ab06 | ||
|
|
0718d76e00 | ||
|
|
a4887c5358 | ||
|
|
2ad5e122ff | ||
|
|
832d874a0e | ||
|
|
6a133d4dbd | ||
|
|
d96dfa63c9 | ||
|
|
d5c8c05426 | ||
|
|
b6bb4647c7 | ||
|
|
a81f06b743 | ||
|
|
cb41a33546 | ||
|
|
005068de9c | ||
|
|
d8c7b1e070 | ||
|
|
4835dfcc42 | ||
|
|
15bceb09a2 | ||
|
|
13e818abfd | ||
|
|
9c5f6049b5 | ||
|
|
2095b619a4 | ||
|
|
df155cbc1b | ||
|
|
3781729fd1 | ||
|
|
d03c634ec0 | ||
|
|
93c979c218 |
@@ -1,12 +1,7 @@
|
||||
FROM elixir:1.16-otp-25
|
||||
FROM elixir:1.17-otp-27
|
||||
|
||||
RUN apt update -yq
|
||||
RUN apt install -yq curl gnupg mc inotify-tools
|
||||
RUN apt install -yq curl gnupg
|
||||
RUN apt --fix-broken install
|
||||
RUN apt remove -y nodejs nodejs-doc
|
||||
RUN curl -sL https://deb.nodesource.com/setup_18.x | bash -
|
||||
RUN apt install -y nodejs
|
||||
RUN npm install --global yarn
|
||||
|
||||
RUN mix local.hex --force
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ version: "0.1"
|
||||
|
||||
services:
|
||||
db:
|
||||
image: postgres:14.3
|
||||
image: postgres:13-alpine
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
@@ -10,13 +10,13 @@ services:
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- db:/var/lib/postgresql/data
|
||||
- db-new:/var/lib/postgresql/data
|
||||
|
||||
wanderer:
|
||||
environment:
|
||||
PORT: 8000
|
||||
DB_HOST: db
|
||||
WEB_APP_URL: "http://localhost:4444"
|
||||
WEB_APP_URL: "http://localhost:8000"
|
||||
ERL_AFLAGS: "-kernel shell_history enabled"
|
||||
build:
|
||||
context: .
|
||||
@@ -33,4 +33,4 @@ services:
|
||||
|
||||
volumes:
|
||||
elixir-artifacts: {}
|
||||
db: {}
|
||||
db-new: {}
|
||||
|
||||
32
.github/workflows/build.yml
vendored
32
.github/workflows/build.yml
vendored
@@ -58,6 +58,8 @@ jobs:
|
||||
otp: ["27"]
|
||||
elixir: ["1.17"]
|
||||
node-version: ["18.x"]
|
||||
outputs:
|
||||
commit_hash: ${{ steps.generate-changelog.outputs.commit_hash }}
|
||||
steps:
|
||||
- name: Prepare
|
||||
run: |
|
||||
@@ -108,16 +110,17 @@ jobs:
|
||||
run: mix compile
|
||||
|
||||
- name: Generate Changelog & Update Tag Version
|
||||
id: generate-changelog
|
||||
run: |
|
||||
git config --global user.name 'CI'
|
||||
git config --global user.email 'ci@users.noreply.github.com'
|
||||
mix git_ops.release --force-patch --yes
|
||||
git push --follow-tags
|
||||
echo "commit_hash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
||||
|
||||
docker:
|
||||
name: 🛠 Build Docker Images
|
||||
needs:
|
||||
- build
|
||||
needs: build
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
checks: write
|
||||
@@ -141,6 +144,7 @@ jobs:
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ needs.build.outputs.commit_hash }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Prepare Changelog
|
||||
@@ -189,6 +193,30 @@ jobs:
|
||||
- name: Image digest
|
||||
run: echo ${{ steps.build.outputs.digest }}
|
||||
|
||||
- uses: markpatterson27/markdown-to-output@v1
|
||||
id: extract-changelog
|
||||
with:
|
||||
filepath: CHANGELOG.md
|
||||
|
||||
- name: Get content
|
||||
uses: 2428392/gh-truncate-string-action@v1.3.0
|
||||
id: get-content
|
||||
with:
|
||||
stringToTruncate: |
|
||||
📣 Wanderer new release available 🎉
|
||||
|
||||
**Version**: ${{ steps.get-latest-tag.outputs.tag }}
|
||||
|
||||
${{ steps.extract-changelog.outputs.body }}
|
||||
maxLength: 500
|
||||
truncationSymbol: "…"
|
||||
|
||||
- name: Discord Webhook Action
|
||||
uses: tsickert/discord-webhook@v5.3.0
|
||||
with:
|
||||
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
content: ${{ steps.get-content.outputs.string }}
|
||||
|
||||
create-release:
|
||||
name: 🏷 Create Release
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
2
.github/workflows/release_actions.yml
vendored
2
.github/workflows/release_actions.yml
vendored
@@ -18,4 +18,4 @@ jobs:
|
||||
key: ${{ secrets.SSH_KEY }}
|
||||
port: ${{ secrets.PORT }}
|
||||
script: |
|
||||
/app/release/linux/deploy.sh ${{ github.event.release.tag_name }}
|
||||
/home/wanderer/app/deploy.sh ${{ github.event.release.tag_name }}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
erlang 25.3
|
||||
elixir 1.16-otp-25
|
||||
erlang 26.2.5.5
|
||||
elixir 1.17.3-otp-26
|
||||
nodejs 18.0.0
|
||||
|
||||
175
CHANGELOG.md
175
CHANGELOG.md
@@ -2,6 +2,156 @@
|
||||
|
||||
<!-- changelog -->
|
||||
|
||||
## [v1.17.0](https://github.com/wanderer-industries/wanderer/compare/v1.16.1...v1.17.0) (2024-11-15)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Signatures: Add user setting to show Description in a separate column
|
||||
|
||||
## [v1.16.1](https://github.com/wanderer-industries/wanderer/compare/v1.16.0...v1.16.1) (2024-11-15)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Signatures: Fix signature stored filters
|
||||
|
||||
## [v1.16.0](https://github.com/wanderer-industries/wanderer/compare/v1.15.5...v1.16.0) (2024-11-15)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Signatures: Add additional filters support to signature list, show description icon
|
||||
|
||||
## [v1.15.5](https://github.com/wanderer-industries/wanderer/compare/v1.15.4...v1.15.5) (2024-11-14)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.15.4](https://github.com/wanderer-industries/wanderer/compare/v1.15.3...v1.15.4) (2024-11-14)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Untracked characters still tracked on map (#63)
|
||||
|
||||
## [v1.15.3](https://github.com/wanderer-industries/wanderer/compare/v1.15.2...v1.15.3) (2024-11-13)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.15.2](https://github.com/wanderer-industries/wanderer/compare/v1.15.1...v1.15.2) (2024-11-07)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.15.1](https://github.com/wanderer-industries/wanderer/compare/v1.15.0...v1.15.1) (2024-11-07)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Dev: Update .devcontainer instructions
|
||||
|
||||
## [v1.15.0](https://github.com/wanderer-industries/wanderer/compare/v1.14.1...v1.15.0) (2024-11-07)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Connections: Add connection mark EOL time (#56)
|
||||
|
||||
## [v1.14.1](https://github.com/wanderer-industries/wanderer/compare/v1.14.0...v1.14.1) (2024-11-06)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fix character tracking permissions
|
||||
|
||||
## [v1.14.0](https://github.com/wanderer-industries/wanderer/compare/v1.13.12...v1.14.0) (2024-11-05)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* ACL: Add an ability to assign member role without DnD
|
||||
|
||||
## [v1.13.12](https://github.com/wanderer-industries/wanderer/compare/v1.13.11...v1.13.12) (2024-11-04)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fix system revert issues
|
||||
|
||||
## [v1.13.11](https://github.com/wanderer-industries/wanderer/compare/v1.13.10...v1.13.11) (2024-11-02)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fix system revert issues
|
||||
|
||||
## [v1.13.10](https://github.com/wanderer-industries/wanderer/compare/v1.13.9...v1.13.10) (2024-11-01)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fix system revert issues
|
||||
|
||||
## [v1.13.9](https://github.com/wanderer-industries/wanderer/compare/v1.13.8...v1.13.9) (2024-11-01)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.13.8](https://github.com/wanderer-industries/wanderer/compare/v1.13.7...v1.13.8) (2024-10-28)
|
||||
|
||||
|
||||
|
||||
## [v1.13.0](https://github.com/wanderer-industries/wanderer/compare/v1.12.11...v1.13.0) (2024-10-28)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Core: Use ESI /characters/affiliation API
|
||||
|
||||
## [v1.12.4](https://github.com/wanderer-industries/wanderer/compare/v1.12.3...v1.12.4) (2024-10-21)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fix systems cleanup
|
||||
|
||||
## [v1.12.3](https://github.com/wanderer-industries/wanderer/compare/v1.12.2...v1.12.3) (2024-10-18)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Map: Fix regression issues
|
||||
|
||||
## [v1.12.1](https://github.com/wanderer-industries/wanderer/compare/v1.12.0...v1.12.1) (2024-10-16)
|
||||
|
||||
|
||||
@@ -20,31 +170,6 @@
|
||||
|
||||
* Map: Prettify user settings
|
||||
|
||||
## [v1.11.5](https://github.com/wanderer-industries/wanderer/compare/v1.11.4...v1.11.5) (2024-10-16)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.11.4](https://github.com/wanderer-industries/wanderer/compare/v1.11.3...v1.11.4) (2024-10-16)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.11.3](https://github.com/wanderer-industries/wanderer/compare/v1.11.2...v1.11.3) (2024-10-16)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.11.2](https://github.com/wanderer-industries/wanderer/compare/v1.11.1...v1.11.2) (2024-10-15)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.11.1](https://github.com/wanderer-industries/wanderer/compare/v1.11.0...v1.11.1) (2024-10-14)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.11.0](https://github.com/wanderer-industries/wanderer/compare/v1.10.0...v1.11.0) (2024-10-14)
|
||||
|
||||
|
||||
|
||||
18
README.md
18
README.md
@@ -20,11 +20,11 @@ Interested to learn more? [Check more on our website](https://wanderer.ltd/news)
|
||||
|
||||
Wanderer is open source project and we have a free as in beer and self-hosted solution called [Wanderer Community Edition (CE)](https://wanderer.ltd/news/community-edition). Here are the differences between Wanderer and Wanderer CE:
|
||||
|
||||
| | Wanderer Cloud | Wanderer Community Edition |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| **Infrastructure management** | Easy and convenient. It takes 2 minutes to register your character and create a map. We manage everything so you don’t have to worry about anything and can focus on gameplay. | You do it all yourself. You need to get a server and you need to manage your infrastructure. You are responsible for installation, maintenance, upgrades, server capacity, uptime, backup, security, stability, consistency, loading time and so on.|
|
||||
| **Release schedule** | Continuously developed and improved with new features and updates multiple times per week. | Latest features and improvements won't be immediately available.|
|
||||
| **Server location** | All visitor data is exclusively processed on EU-owned cloud infrastructure. We keep your site data on a secure, encrypted and green energy powered server in Germany. This ensures that your site data is protected by the strict European Union data privacy laws and ensures compliance with GDPR. Your website data never leaves the EU. | You have full control and can host your instance on any server in any country that you wish. Host it on a server in your basement or host it with any cloud provider wherever you want, even those that are not GDPR compliant.|
|
||||
| | Wanderer Cloud | Wanderer Community Edition |
|
||||
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Infrastructure management** | Easy and convenient. It takes 2 minutes to register your character and create a map. We manage everything so you don’t have to worry about anything and can focus on gameplay. | You do it all yourself. You need to get a server and you need to manage your infrastructure. You are responsible for installation, maintenance, upgrades, server capacity, uptime, backup, security, stability, consistency, loading time and so on. |
|
||||
| **Release schedule** | Continuously developed and improved with new features and updates multiple times per week. | Latest features and improvements won't be immediately available. |
|
||||
| **Server location** | All visitor data is exclusively processed on EU-owned cloud infrastructure. We keep your site data on a secure, encrypted and green energy powered server in Germany. This ensures that your site data is protected by the strict European Union data privacy laws and ensures compliance with GDPR. Your website data never leaves the EU. | You have full control and can host your instance on any server in any country that you wish. Host it on a server in your basement or host it with any cloud provider wherever you want, even those that are not GDPR compliant. |
|
||||
|
||||
Interested in self-hosting Wanderer CE on your server? Take a look at our [Wanderer CE installation instructions](https://github.com/wanderer-industries/community-edition/).
|
||||
|
||||
@@ -54,7 +54,13 @@ Now you can visit [`localhost:8000`](http://localhost:8000) from your browser.
|
||||
#### Using .devcontainer
|
||||
|
||||
- Run devcontainer
|
||||
- See how to start server in #setup section
|
||||
- Install additional dependencies inside Dev container
|
||||
- `root@0d0a785313b6:/app# apt update`
|
||||
- `root@0d0a785313b6:/app# curl -sL https://deb.nodesource.com/setup_18.x | bash -`
|
||||
- `root@0d0a785313b6:/app# apt-get install nodejs inotify-tools -y`
|
||||
- `root@0d0a785313b6:/app# mix setup`
|
||||
|
||||
- See how to run server in #Run section
|
||||
|
||||
#### Using nix flakes
|
||||
|
||||
|
||||
@@ -85,3 +85,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
.p-dropdown-label, .p-inputtext {
|
||||
padding: 0.25rem 0.75rem;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.p-dropdown-item {
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.p-dropdown-item-group {
|
||||
padding: 0.25rem 0.75rem;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.p-dropdown-trigger {
|
||||
width: 14px;
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
.p-dropdown-empty-message {
|
||||
padding: 0.25rem 0.5rem;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ export const useTagMenu = (
|
||||
ref.current = { onSystemTag, systems, systemId };
|
||||
|
||||
return useCallback(() => {
|
||||
const { onSystemTag, systemId , systems} = ref.current;
|
||||
const { onSystemTag, systemId, systems } = ref.current;
|
||||
const system = systemId ? getSystemById(systems, systemId) : undefined;
|
||||
|
||||
const isSelectedLetters = AVAILABLE_LETTERS.includes(system?.tag ?? '');
|
||||
|
||||
@@ -4,6 +4,7 @@ import { OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.
|
||||
import { SolarSystemRawType } from '@/hooks/Mapper/types';
|
||||
import { WaypointSetContextHandler } from '@/hooks/Mapper/components/contexts/types.ts';
|
||||
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
||||
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
|
||||
|
||||
interface UseContextMenuSystemHandlersProps {
|
||||
hubs: string[];
|
||||
@@ -16,8 +17,10 @@ export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseC
|
||||
|
||||
const [system, setSystem] = useState<string>();
|
||||
|
||||
const ref = useRef({ hubs, system, systems, outCommand });
|
||||
ref.current = { hubs, system, systems, outCommand };
|
||||
const { deleteSystems } = useDeleteSystems();
|
||||
|
||||
const ref = useRef({ hubs, system, systems, outCommand, deleteSystems });
|
||||
ref.current = { hubs, system, systems, outCommand, deleteSystems };
|
||||
|
||||
const open = useCallback((ev: any, systemId: string) => {
|
||||
setSystem(systemId);
|
||||
@@ -27,12 +30,12 @@ export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseC
|
||||
}, []);
|
||||
|
||||
const onDeleteSystem = useCallback(() => {
|
||||
const { system, outCommand } = ref.current;
|
||||
const { system, deleteSystems } = ref.current;
|
||||
if (!system) {
|
||||
return;
|
||||
}
|
||||
|
||||
outCommand({ type: OutCommand.deleteSystems, data: [system] });
|
||||
deleteSystems([system]);
|
||||
setSystem(undefined);
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { getSystemById } from '@/hooks/Mapper/helpers';
|
||||
import { useWaypointMenu } from '@/hooks/Mapper/components/contexts/hooks';
|
||||
import { WaypointSetContextHandler } from '@/hooks/Mapper/components/contexts/types.ts';
|
||||
import { FastSystemActions } from '@/hooks/Mapper/components/contexts/components';
|
||||
import { useJumpPlannerMenu } from '@/hooks/Mapper/components/contexts/hooks/useJumpPlannerMenu';
|
||||
import { useJumpPlannerMenu } from '@/hooks/Mapper/components/contexts/hooks';
|
||||
import { Route } from '@/hooks/Mapper/types/routes.ts';
|
||||
|
||||
export interface ContextMenuSystemInfoProps {
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { Node } from 'reactflow';
|
||||
import { useRef, useState } from 'react';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { ContextMenu } from 'primereact/contextmenu';
|
||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { SolarSystemRawType } from '@/hooks/Mapper/types';
|
||||
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { NodeSelectionMouseHandler } from '@/hooks/Mapper/components/contexts/types.ts';
|
||||
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
|
||||
|
||||
export const useContextMenuSystemMultipleHandlers = () => {
|
||||
const contextMenuRef = useRef<ContextMenu | null>(null);
|
||||
const { outCommand } = useMapRootState();
|
||||
const [systems, setSystems] = useState<Node<SolarSystemRawType>[]>();
|
||||
|
||||
const { deleteSystems } = useDeleteSystems();
|
||||
|
||||
const handleSystemMultipleContext: NodeSelectionMouseHandler = (ev, systems_) => {
|
||||
setSystems(systems_);
|
||||
ev.preventDefault();
|
||||
@@ -19,7 +19,7 @@ export const useContextMenuSystemMultipleHandlers = () => {
|
||||
contextMenuRef.current?.show(ev);
|
||||
};
|
||||
|
||||
const onDeleteSystems = () => {
|
||||
const onDeleteSystems = useCallback(() => {
|
||||
if (!systems) {
|
||||
return;
|
||||
}
|
||||
@@ -29,12 +29,11 @@ export const useContextMenuSystemMultipleHandlers = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
outCommand({ type: OutCommand.deleteSystems, data: sysToDel });
|
||||
};
|
||||
deleteSystems(sysToDel);
|
||||
}, [deleteSystems, systems]);
|
||||
|
||||
return {
|
||||
handleSystemMultipleContext,
|
||||
|
||||
contextMenuRef,
|
||||
onDeleteSystems,
|
||||
};
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
export * from './useWaypointMenu';
|
||||
export * from './useJumpPlannerMenu';
|
||||
export * from './useDeleteSystems';
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
|
||||
export const useDeleteSystems = () => {
|
||||
const { outCommand } = useMapRootState();
|
||||
|
||||
const deleteSystems = (systemIds: string[]) => {
|
||||
if (!systemIds || !systemIds.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
outCommand({ type: OutCommand.deleteSystems, data: systemIds });
|
||||
};
|
||||
|
||||
return {
|
||||
deleteSystems,
|
||||
};
|
||||
};
|
||||
2
assets/js/hooks/Mapper/components/hooks/index.ts
Normal file
2
assets/js/hooks/Mapper/components/hooks/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './useSystemInfo';
|
||||
export * from './useGetOwnOnlineCharacters';
|
||||
33
assets/js/hooks/Mapper/components/hooks/useSystemInfo.ts
Normal file
33
assets/js/hooks/Mapper/components/hooks/useSystemInfo.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useMemo } from 'react';
|
||||
import { getSystemById } from '@/hooks/Mapper/helpers';
|
||||
import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts';
|
||||
|
||||
interface UseSystemInfoProps {
|
||||
systemId: string;
|
||||
}
|
||||
|
||||
export const useSystemInfo = ({ systemId }: UseSystemInfoProps) => {
|
||||
const {
|
||||
data: { systems, connections },
|
||||
} = useMapRootState();
|
||||
|
||||
const { systems: systemStatics } = useLoadSystemStatic({ systems: [systemId] });
|
||||
|
||||
return useMemo(() => {
|
||||
const staticInfo = systemStatics.get(parseInt(systemId));
|
||||
const dynamicInfo = getSystemById(systems, systemId);
|
||||
|
||||
if (!staticInfo || !dynamicInfo) {
|
||||
throw new Error(`Error on getting system ${systemId}`);
|
||||
}
|
||||
|
||||
const leadsTo = connections
|
||||
.filter(x => [x.source, x.target].includes(systemId))
|
||||
.map(x => [x.source, x.target])
|
||||
.flat()
|
||||
.filter(x => x !== systemId);
|
||||
|
||||
return { dynamicInfo, staticInfo, leadsTo };
|
||||
}, [systemStatics, systemId, systems, connections]);
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ForwardedRef, forwardRef, MouseEvent, useCallback, useEffect } from 'react';
|
||||
import { ForwardedRef, forwardRef, MouseEvent, useCallback, useEffect, useRef } from 'react';
|
||||
import ReactFlow, {
|
||||
Background,
|
||||
ConnectionMode,
|
||||
@@ -13,12 +13,15 @@ import ReactFlow, {
|
||||
SelectionMode,
|
||||
useEdgesState,
|
||||
useNodesState,
|
||||
NodeChange,
|
||||
useReactFlow,
|
||||
} from 'reactflow';
|
||||
import 'reactflow/dist/style.css';
|
||||
import classes from './Map.module.scss';
|
||||
import './styles/neon-theme.scss';
|
||||
import './styles/eve-common.scss';
|
||||
import { MapProvider, useMapState } from './MapProvider';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useMapHandlers, useUpdateNodes } from './hooks';
|
||||
import { MapHandlers, OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import {
|
||||
@@ -34,6 +37,7 @@ import { SESSION_KEY } from '@/hooks/Mapper/constants.ts';
|
||||
import { SolarSystemConnection, SolarSystemRawType } from '@/hooks/Mapper/types';
|
||||
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
||||
import { NodeSelectionMouseHandler } from '@/hooks/Mapper/components/contexts/types.ts';
|
||||
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
|
||||
|
||||
const DEFAULT_VIEW_PORT = { zoom: 1, x: 0, y: 0 };
|
||||
|
||||
@@ -108,6 +112,7 @@ const MapComp = ({
|
||||
isShowMinimap,
|
||||
showKSpaceBG,
|
||||
}: MapCompProps) => {
|
||||
const { getNode } = useReactFlow();
|
||||
const [nodes, , onNodesChange] = useNodesState<SolarSystemRawType>(initialNodes);
|
||||
const [edges, , onEdgesChange] = useEdgesState<Edge<SolarSystemConnection>[]>(initialEdges);
|
||||
|
||||
@@ -115,8 +120,15 @@ const MapComp = ({
|
||||
useUpdateNodes(nodes);
|
||||
const { handleRootContext, ...rootCtxProps } = useContextMenuRootHandlers();
|
||||
const { handleConnectionContext, ...connectionCtxProps } = useContextMenuConnectionHandlers();
|
||||
|
||||
const { update } = useMapState();
|
||||
const {
|
||||
data: { systems },
|
||||
} = useMapRootState();
|
||||
|
||||
const { deleteSystems } = useDeleteSystems();
|
||||
|
||||
const systemsRef = useRef({ systems });
|
||||
systemsRef.current = { systems };
|
||||
|
||||
const onConnect: OnConnect = useCallback(
|
||||
params => {
|
||||
@@ -171,6 +183,32 @@ const MapComp = ({
|
||||
localStorage.setItem(SESSION_KEY.viewPort, JSON.stringify(viewport));
|
||||
};
|
||||
|
||||
const handleNodesChange = useCallback(
|
||||
(changes: NodeChange[]) => {
|
||||
const systemsIdsToRemove: string[] = [];
|
||||
const nextChanges = changes.reduce((acc, change) => {
|
||||
if (change.type === 'remove') {
|
||||
const node = getNode(change.id);
|
||||
const { systems = [] } = systemsRef.current;
|
||||
if (node?.data?.id && !systems.map(s => s.id).includes(node?.data?.id)) {
|
||||
return [...acc, change];
|
||||
} else if (!node?.data?.locked) {
|
||||
systemsIdsToRemove.push(node?.data?.id);
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
return [...acc, change];
|
||||
}, [] as NodeChange[]);
|
||||
|
||||
if (systemsIdsToRemove.length) {
|
||||
deleteSystems(systemsIdsToRemove);
|
||||
}
|
||||
|
||||
onNodesChange(nextChanges);
|
||||
},
|
||||
[deleteSystems, getNode, onNodesChange],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
update(x => ({
|
||||
...x,
|
||||
@@ -184,7 +222,7 @@ const MapComp = ({
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onNodesChange={handleNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={onConnect}
|
||||
// TODO we need save into session all of this
|
||||
@@ -219,10 +257,10 @@ const MapComp = ({
|
||||
minZoom={0.2}
|
||||
maxZoom={1.5}
|
||||
elevateNodesOnSelect
|
||||
deleteKeyCode={['Delete']}
|
||||
// TODO need create clear example with problem with that flag
|
||||
// if system is not visible edge not drawing (and any render in Custom node is not happening)
|
||||
// onlyRenderVisibleElements
|
||||
deleteKeyCode={null}
|
||||
selectionMode={SelectionMode.Partial}
|
||||
>
|
||||
{isShowMinimap && <MiniMap pannable zoomable ariaLabel="Mini map" className={minimapClasses} />}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "@/hooks/Mapper/components/map/styles/eve-common-variables";
|
||||
@import '@/hooks/Mapper/components/map/styles/eve-common-variables';
|
||||
|
||||
$pastel-blue: #5a7d9a;
|
||||
$pastel-pink: #d291bc;
|
||||
@@ -25,9 +25,11 @@ $tooltip-bg: #202020; // Темный фон для подсказок
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
|
||||
|
||||
&.Mataria, &.Amarria, &.Gallente, &.Caldaria {
|
||||
&::Before {
|
||||
&.Mataria,
|
||||
&.Amarria,
|
||||
&.Gallente,
|
||||
&.Caldaria {
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -44,42 +46,40 @@ $tooltip-bg: #202020; // Темный фон для подсказок
|
||||
|
||||
&.Mataria {
|
||||
&::before {
|
||||
background-image: url("/images/mataria.png");
|
||||
background-image: url('/images/mataria-180.png');
|
||||
opacity: 0.6;
|
||||
background-position-x: -28px;
|
||||
background-position-y: -3px;
|
||||
background-position-x: 1px;
|
||||
background-position-y: -14px;
|
||||
}
|
||||
}
|
||||
|
||||
&.Caldaria {
|
||||
&::before {
|
||||
background-image: url("/images/caldaria.png");
|
||||
background-image: url('/images/caldaria-180.png');
|
||||
opacity: 0.6;
|
||||
background-position-x: -16px;
|
||||
background-position-y: -17px;
|
||||
background-position-x: 1px;
|
||||
background-position-y: -10px;
|
||||
}
|
||||
}
|
||||
|
||||
&.Amarria {
|
||||
&::before {
|
||||
opacity: 0.45;
|
||||
background-image: url("/images/amarr.png");
|
||||
background-position-x: 0px;
|
||||
background-position-y: -1px;
|
||||
width: calc(100% + 10px)
|
||||
background-image: url('/images/amarr-180.png');
|
||||
background-position-x: 0;
|
||||
background-position-y: -13px;
|
||||
}
|
||||
}
|
||||
|
||||
&.Gallente {
|
||||
&::before {
|
||||
opacity: 0.6;
|
||||
background-image: url("/images/gallente.png");
|
||||
background-position-x: -1px;
|
||||
background-position-y: -10px;
|
||||
opacity: 0.5;
|
||||
background-image: url('/images/gallente-180.png');
|
||||
background-position-x: 1px;
|
||||
background-position-y: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&.selected {
|
||||
border-color: $pastel-pink;
|
||||
box-shadow: 0 0 10px #9a1af1c2;
|
||||
@@ -95,7 +95,7 @@ $tooltip-bg: #202020; // Темный фон для подсказок
|
||||
|
||||
&.eve-system-status-home {
|
||||
border: 1px solid darken($eve-solar-system-status-color-home, 30%);
|
||||
background-image: linear-gradient(45deg, $eve-solar-system-status-friendly, transparent);
|
||||
background-image: linear-gradient(275deg, $eve-solar-system-status-friendly, transparent);
|
||||
|
||||
&.selected {
|
||||
border-color: $eve-solar-system-status-color-home;
|
||||
@@ -104,7 +104,7 @@ $tooltip-bg: #202020; // Темный фон для подсказок
|
||||
|
||||
&.eve-system-status-friendly {
|
||||
border: 1px solid darken($eve-solar-system-status-color-friendly, 20%);
|
||||
background-image: linear-gradient(45deg, darken($eve-solar-system-status-friendly, 30%), transparent);
|
||||
background-image: linear-gradient(275deg, darken($eve-solar-system-status-friendly, 30%), transparent);
|
||||
|
||||
&.selected {
|
||||
border-color: darken($eve-solar-system-status-color-friendly, 5%);
|
||||
@@ -113,7 +113,7 @@ $tooltip-bg: #202020; // Темный фон для подсказок
|
||||
|
||||
&.eve-system-status-lookingFor {
|
||||
border: 1px solid darken($eve-solar-system-status-color-lookingFor, 15%);
|
||||
background-image: linear-gradient(45deg, #45ff8f2f, #457fff2f);
|
||||
background-image: linear-gradient(275deg, #45ff8f2f, #457fff2f);
|
||||
|
||||
&.selected {
|
||||
border-color: $pastel-pink;
|
||||
@@ -121,17 +121,16 @@ $tooltip-bg: #202020; // Темный фон для подсказок
|
||||
}
|
||||
|
||||
&.eve-system-status-warning {
|
||||
background-image: linear-gradient(45deg, $eve-solar-system-status-warning, transparent);
|
||||
background-image: linear-gradient(275deg, $eve-solar-system-status-warning, transparent);
|
||||
}
|
||||
|
||||
&.eve-system-status-dangerous {
|
||||
background-image: linear-gradient(45deg, $eve-solar-system-status-dangerous, transparent);
|
||||
background-image: linear-gradient(275deg, $eve-solar-system-status-dangerous, transparent);
|
||||
}
|
||||
|
||||
&.eve-system-status-target {
|
||||
background-image: linear-gradient(45deg, $eve-solar-system-status-target, transparent);
|
||||
background-image: linear-gradient(275deg, $eve-solar-system-status-target, transparent);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.Bookmarks {
|
||||
@@ -158,7 +157,7 @@ $tooltip-bg: #202020; // Темный фон для подсказок
|
||||
//background-color: #833ca4;
|
||||
|
||||
&:not(:first-child) {
|
||||
box-shadow: inset 4px -3px 4px rgba(0, 0, 0, .3);
|
||||
box-shadow: inset 4px -3px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +180,6 @@ $tooltip-bg: #202020; // Темный фон для подсказок
|
||||
font-size: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.icon {
|
||||
@@ -219,9 +217,7 @@ $tooltip-bg: #202020; // Темный фон для подсказок
|
||||
}
|
||||
|
||||
.solarSystemName {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.BottomRow {
|
||||
@@ -288,11 +284,19 @@ $tooltip-bg: #202020; // Темный фон для подсказок
|
||||
border-color: $pastel-pink;
|
||||
}
|
||||
|
||||
&.HandleTop { top: -2px }
|
||||
&.HandleTop {
|
||||
top: -2px;
|
||||
}
|
||||
|
||||
&.HandleRight { right: -2px }
|
||||
&.HandleRight {
|
||||
right: -2px;
|
||||
}
|
||||
|
||||
&.HandleBottom { bottom: -2px }
|
||||
&.HandleBottom {
|
||||
bottom: -2px;
|
||||
}
|
||||
|
||||
&.HandleLeft { left: -2px }
|
||||
&.HandleLeft {
|
||||
left: -2px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
|
||||
<div className={classes.Bookmarks}>
|
||||
{labelCustom !== '' && (
|
||||
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.custom)}>
|
||||
<div>{labelCustom}</div>
|
||||
<span className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] ">{labelCustom}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -168,14 +168,16 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
|
||||
{visible && (
|
||||
<>
|
||||
<div className={classes.HeadRow}>
|
||||
<div className={clsx(classes.classTitle, classTitleColor)}>{class_title ?? '-'}</div>
|
||||
<div className={clsx(classes.classTitle, classTitleColor, '[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]')}>
|
||||
{class_title ?? '-'}
|
||||
</div>
|
||||
{tag != null && tag !== '' && (
|
||||
<div className={clsx(classes.TagTitle, 'text-sky-400 font-medium')}>{tag}</div>
|
||||
)}
|
||||
<div
|
||||
className={clsx(
|
||||
classes.classSystemName,
|
||||
'flex-grow overflow-hidden text-ellipsis whitespace-nowrap font-sans',
|
||||
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] flex-grow overflow-hidden text-ellipsis whitespace-nowrap font-sans',
|
||||
)}
|
||||
>
|
||||
{solar_system_name}
|
||||
@@ -196,16 +198,16 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
|
||||
|
||||
<div className={clsx(classes.BottomRow, 'flex items-center justify-between')}>
|
||||
{customName && (
|
||||
<div className="text-blue-300 whitespace-nowrap overflow-hidden text-ellipsis mr-0.5">{customName}</div>
|
||||
<div className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] text-blue-300 whitespace-nowrap overflow-hidden text-ellipsis mr-0.5">
|
||||
{customName}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isWormhole && !customName && (
|
||||
<div
|
||||
className={clsx('text-stone-400 whitespace-nowrap overflow-hidden text-ellipsis mr-0.5', {
|
||||
['text-teal-100 font-bold']: space === Spaces.Caldari,
|
||||
['text-yellow-100 font-bold']: space === Spaces.Amarr || space === Spaces.Matar,
|
||||
['text-lime-200/80 font-bold']: space === Spaces.Gallente,
|
||||
})}
|
||||
className={clsx(
|
||||
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] text-stone-300 whitespace-nowrap overflow-hidden text-ellipsis mr-0.5',
|
||||
)}
|
||||
>
|
||||
{region_name}
|
||||
</div>
|
||||
@@ -215,10 +217,10 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
|
||||
|
||||
<div className="flex items-center justify-end">
|
||||
<div className="flex gap-1 items-center">
|
||||
{locked && <i className={PrimeIcons.LOCK} style={{ fontSize: '0.45rem' }}></i>}
|
||||
{locked && <i className={PrimeIcons.LOCK} style={{ fontSize: '0.45rem', fontWeight: 'bold' }}></i>}
|
||||
|
||||
{hubs.includes(solar_system_id.toString()) && (
|
||||
<i className={PrimeIcons.MAP_MARKER} style={{ fontSize: '0.45rem' }}></i>
|
||||
<i className={PrimeIcons.MAP_MARKER} style={{ fontSize: '0.45rem', fontWeight: 'bold' }}></i>
|
||||
)}
|
||||
|
||||
{charactersInSystem.length > 0 && (
|
||||
|
||||
@@ -18,5 +18,9 @@ export const WormholeClassComp = ({ id }: WormholeClassComp) => {
|
||||
}
|
||||
|
||||
const colorClass = WORMHOLE_CLASS_STYLES[wormholeDataAdditional.wormholeClassID.toString()];
|
||||
return <div className={clsx(colorClass)}>{wormholeDataAdditional.shortName}</div>;
|
||||
return (
|
||||
<div className={clsx(colorClass, '[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]')}>
|
||||
{wormholeDataAdditional.shortName}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -30,11 +30,77 @@ export enum SOLAR_SYSTEM_CLASS_IDS {
|
||||
zarzakh = 10100,
|
||||
}
|
||||
|
||||
export enum SOLAR_SYSTEM_CLASS_GROUPS {
|
||||
ccp = 'ccp',
|
||||
c1 = 'c1',
|
||||
c2 = 'c2',
|
||||
c3 = 'c3',
|
||||
c4 = 'c4',
|
||||
c5 = 'c5',
|
||||
c6 = 'c6',
|
||||
hs = 'hs',
|
||||
ls = 'ls',
|
||||
ns = 'ns',
|
||||
thera = 'thera',
|
||||
c13 = 'c13',
|
||||
drifter = 'drifter',
|
||||
unknown = 'unknown',
|
||||
pochven = 'pochven',
|
||||
jovian = 'jovian',
|
||||
}
|
||||
|
||||
export const SOLAR_SYSTEM_TO_CLASS_GROUPS_CLASSES = {
|
||||
c1: ['c1'],
|
||||
c2: ['c2'],
|
||||
c3: ['c3'],
|
||||
c4: ['c4'],
|
||||
c5: ['c5'],
|
||||
c6: ['c6'],
|
||||
hs: ['hs'],
|
||||
ls: ['ls'],
|
||||
ns: ['ns'],
|
||||
thera: ['thera'],
|
||||
c13: ['c13'],
|
||||
pochven: ['pochven'],
|
||||
drifter: ['sentinel', 'barbican', 'vidette', 'conflux', 'redoubt'],
|
||||
jove: ['jove'],
|
||||
};
|
||||
|
||||
export const SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS = {
|
||||
ccp1: SOLAR_SYSTEM_CLASS_GROUPS.ccp,
|
||||
c1: SOLAR_SYSTEM_CLASS_GROUPS.c1,
|
||||
c2: SOLAR_SYSTEM_CLASS_GROUPS.c2,
|
||||
c3: SOLAR_SYSTEM_CLASS_GROUPS.c3,
|
||||
c4: SOLAR_SYSTEM_CLASS_GROUPS.c4,
|
||||
c5: SOLAR_SYSTEM_CLASS_GROUPS.c5,
|
||||
c6: SOLAR_SYSTEM_CLASS_GROUPS.c6,
|
||||
hs: SOLAR_SYSTEM_CLASS_GROUPS.hs,
|
||||
ls: SOLAR_SYSTEM_CLASS_GROUPS.ls,
|
||||
ns: SOLAR_SYSTEM_CLASS_GROUPS.ns,
|
||||
ccp2: SOLAR_SYSTEM_CLASS_GROUPS.ccp,
|
||||
ccp3: SOLAR_SYSTEM_CLASS_GROUPS.ccp,
|
||||
thera: SOLAR_SYSTEM_CLASS_GROUPS.thera,
|
||||
c13: SOLAR_SYSTEM_CLASS_GROUPS.c13,
|
||||
sentinel: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
|
||||
baribican: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
|
||||
vidette: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
|
||||
conflux: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
|
||||
redoubt: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
|
||||
a1: SOLAR_SYSTEM_CLASS_GROUPS.unknown,
|
||||
a2: SOLAR_SYSTEM_CLASS_GROUPS.unknown,
|
||||
a3: SOLAR_SYSTEM_CLASS_GROUPS.unknown,
|
||||
a4: SOLAR_SYSTEM_CLASS_GROUPS.unknown,
|
||||
a5: SOLAR_SYSTEM_CLASS_GROUPS.unknown,
|
||||
ccp4: SOLAR_SYSTEM_CLASS_GROUPS.ccp,
|
||||
pochven: SOLAR_SYSTEM_CLASS_GROUPS.pochven,
|
||||
};
|
||||
|
||||
type WormholesAdditionalInfoType = {
|
||||
id: string;
|
||||
shortName: string;
|
||||
wormholeClassID: number;
|
||||
title: string;
|
||||
shortTitle: string;
|
||||
effectPower?: number;
|
||||
};
|
||||
|
||||
@@ -45,6 +111,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
|
||||
shortName: 'CCP',
|
||||
wormholeClassID: -1,
|
||||
title: 'CCP System',
|
||||
shortTitle: 'CCP',
|
||||
},
|
||||
{
|
||||
id: 'c1',
|
||||
@@ -52,6 +119,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
|
||||
wormholeClassID: 1,
|
||||
effectPower: 1,
|
||||
title: 'Class 1',
|
||||
shortTitle: 'C1',
|
||||
},
|
||||
{
|
||||
id: 'c2',
|
||||
@@ -59,6 +127,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
|
||||
wormholeClassID: 2,
|
||||
effectPower: 2,
|
||||
title: 'Class 2',
|
||||
shortTitle: 'C2',
|
||||
},
|
||||
{
|
||||
id: 'c3',
|
||||
@@ -66,6 +135,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
|
||||
wormholeClassID: 3,
|
||||
effectPower: 3,
|
||||
title: 'Class 3',
|
||||
shortTitle: 'C3',
|
||||
},
|
||||
{
|
||||
id: 'c4',
|
||||
@@ -73,6 +143,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
|
||||
wormholeClassID: 4,
|
||||
effectPower: 4,
|
||||
title: 'Class 4',
|
||||
shortTitle: 'C4',
|
||||
},
|
||||
{
|
||||
id: 'c5',
|
||||
@@ -80,6 +151,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
|
||||
wormholeClassID: 5,
|
||||
effectPower: 5,
|
||||
title: 'Class 5',
|
||||
shortTitle: 'C5',
|
||||
},
|
||||
{
|
||||
id: 'c6',
|
||||
@@ -87,42 +159,49 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
|
||||
wormholeClassID: 6,
|
||||
effectPower: 6,
|
||||
title: 'Class 6',
|
||||
shortTitle: 'C6',
|
||||
},
|
||||
{
|
||||
id: 'hs',
|
||||
shortName: 'H',
|
||||
wormholeClassID: 7,
|
||||
title: 'High-sec',
|
||||
shortTitle: 'High-sec',
|
||||
},
|
||||
{
|
||||
id: 'ls',
|
||||
shortName: 'L',
|
||||
wormholeClassID: 8,
|
||||
title: 'Low-sec',
|
||||
shortTitle: 'Low-sec',
|
||||
},
|
||||
{
|
||||
id: 'ns',
|
||||
shortName: 'N',
|
||||
wormholeClassID: 9,
|
||||
title: 'Null-sec',
|
||||
shortTitle: 'Null-sec',
|
||||
},
|
||||
{
|
||||
id: 'ccp2',
|
||||
shortName: 'CCP',
|
||||
wormholeClassID: 10,
|
||||
title: 'CCP System',
|
||||
shortTitle: 'CCP',
|
||||
},
|
||||
{
|
||||
id: 'ccp3',
|
||||
shortName: 'CCP',
|
||||
wormholeClassID: 11,
|
||||
title: 'CCP System',
|
||||
shortTitle: 'CCP',
|
||||
},
|
||||
{
|
||||
id: 'thera',
|
||||
shortName: 'T',
|
||||
wormholeClassID: 12,
|
||||
title: 'Class 12 (Thera)',
|
||||
shortTitle: 'Thera',
|
||||
},
|
||||
{
|
||||
id: 'c13',
|
||||
@@ -130,6 +209,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
|
||||
wormholeClassID: 13,
|
||||
effectPower: 6,
|
||||
title: 'Class 13 (Shattered Frigate)',
|
||||
shortTitle: 'C13',
|
||||
},
|
||||
{
|
||||
id: 'sentinel',
|
||||
@@ -137,6 +217,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
|
||||
wormholeClassID: 14,
|
||||
effectPower: 2,
|
||||
title: 'Class 14 (Sentinel Drifter)',
|
||||
shortTitle: 'Sentinel',
|
||||
},
|
||||
{
|
||||
id: 'barbican',
|
||||
@@ -144,6 +225,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
|
||||
wormholeClassID: 15,
|
||||
effectPower: 2,
|
||||
title: 'Class 15 (Barbican Drifter)',
|
||||
shortTitle: 'Barbican',
|
||||
},
|
||||
{
|
||||
id: 'vidette',
|
||||
@@ -151,6 +233,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
|
||||
wormholeClassID: 16,
|
||||
effectPower: 2,
|
||||
title: 'Class 16 (Vidette Drifter)',
|
||||
shortTitle: 'Vidette',
|
||||
},
|
||||
{
|
||||
id: 'conflux',
|
||||
@@ -158,6 +241,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
|
||||
wormholeClassID: 17,
|
||||
effectPower: 2,
|
||||
title: 'Class 17 (Conflux Drifter)',
|
||||
shortTitle: 'Conflux',
|
||||
},
|
||||
{
|
||||
id: 'redoubt',
|
||||
@@ -165,59 +249,79 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
|
||||
wormholeClassID: 18,
|
||||
effectPower: 2,
|
||||
title: 'Class 18 (Redoubt Drifter)',
|
||||
shortTitle: 'Redoubt',
|
||||
},
|
||||
{
|
||||
id: 'a1',
|
||||
shortName: 'A1',
|
||||
wormholeClassID: 19,
|
||||
title: '(Abyssal class 1)',
|
||||
shortTitle: 'A1',
|
||||
},
|
||||
{
|
||||
id: 'a2',
|
||||
shortName: 'A2',
|
||||
wormholeClassID: 20,
|
||||
title: '(Abyssal class 2)',
|
||||
shortTitle: 'A2',
|
||||
},
|
||||
{
|
||||
id: 'a3',
|
||||
shortName: 'A3',
|
||||
wormholeClassID: 21,
|
||||
title: '(Abyssal class 3)',
|
||||
shortTitle: 'A3',
|
||||
},
|
||||
{
|
||||
id: 'a4',
|
||||
shortName: 'A4',
|
||||
wormholeClassID: 22,
|
||||
title: '(Abyssal class 4)',
|
||||
shortTitle: 'A4',
|
||||
},
|
||||
{
|
||||
id: 'a5',
|
||||
shortName: 'A5',
|
||||
wormholeClassID: 23,
|
||||
title: '(Abyssal class 5)',
|
||||
shortTitle: 'A5',
|
||||
},
|
||||
{
|
||||
id: 'ccp4',
|
||||
shortName: 'CCP',
|
||||
wormholeClassID: 24,
|
||||
title: 'CCP System (Penalty)',
|
||||
shortTitle: 'CCP',
|
||||
},
|
||||
{
|
||||
id: 'pochven',
|
||||
shortName: 'P',
|
||||
wormholeClassID: 25,
|
||||
title: 'Triglavian space (Pochven)',
|
||||
shortTitle: 'Pochven',
|
||||
},
|
||||
{
|
||||
id: 'zarzakh',
|
||||
shortName: 'N',
|
||||
wormholeClassID: 10100,
|
||||
title: 'Pirate space',
|
||||
shortTitle: 'Zarzakh',
|
||||
},
|
||||
{
|
||||
id: 'k162',
|
||||
shortName: 'K162',
|
||||
wormholeClassID: 10101,
|
||||
title: 'Reverse',
|
||||
shortTitle: 'K162',
|
||||
},
|
||||
];
|
||||
|
||||
export const WORMHOLES_ADDITIONAL_INFO: Record<string, WormholesAdditionalInfoType> =
|
||||
WORMHOLES_ADDITIONAL_INFO_RAW.reduce((acc, x) => ({ ...acc, [x.id]: x }), {});
|
||||
|
||||
export const WORMHOLES_ADDITIONAL_INFO_BY_CLASS_ID: Record<string, WormholesAdditionalInfoType> =
|
||||
WORMHOLES_ADDITIONAL_INFO_RAW.reduce((acc, x) => ({ ...acc, [x.wormholeClassID]: x }), {});
|
||||
|
||||
// export const SOLAR_SYSTEM_CLASS_NAMES = {
|
||||
// ccp1 = ,
|
||||
// c1 = ,
|
||||
|
||||
@@ -2,28 +2,17 @@ import { Node, useReactFlow } from 'reactflow';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { CommandAddSystems } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { convertSystem2Node } from '../../helpers';
|
||||
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
|
||||
|
||||
export const useMapAddSystems = () => {
|
||||
const rf = useReactFlow();
|
||||
const {
|
||||
data: { systems },
|
||||
update,
|
||||
} = useMapState();
|
||||
|
||||
const ref = useRef({ rf, systems, update });
|
||||
ref.current = { update, systems, rf };
|
||||
const ref = useRef({ rf });
|
||||
ref.current = { rf };
|
||||
|
||||
return useCallback(
|
||||
(systems: CommandAddSystems) => {
|
||||
const nodes = rf.getNodes();
|
||||
const prepared: Node[] = systems.filter(x => !nodes.some(y => x.id === y.id)).map(convertSystem2Node);
|
||||
rf.addNodes(prepared);
|
||||
|
||||
ref.current.update({
|
||||
systems: [...ref.current.systems.filter(sys => systems.some(x => sys.id !== x.id)), ...systems],
|
||||
});
|
||||
},
|
||||
[rf],
|
||||
);
|
||||
return useCallback((systems: CommandAddSystems) => {
|
||||
const { rf } = ref.current;
|
||||
const nodes = rf.getNodes();
|
||||
const prepared: Node[] = systems.filter(x => !nodes.some(y => x.id === y.id)).map(convertSystem2Node);
|
||||
rf.addNodes(prepared);
|
||||
}, []);
|
||||
};
|
||||
|
||||
@@ -18,6 +18,9 @@ import {
|
||||
CommandUpdateSystems,
|
||||
MapHandlers,
|
||||
} from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
|
||||
import { useMapEventListener } from '@/hooks/Mapper/events';
|
||||
|
||||
import {
|
||||
useCommandsCharacters,
|
||||
useCommandsConnections,
|
||||
@@ -57,16 +60,13 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
|
||||
mapInit(data as CommandInit);
|
||||
break;
|
||||
case Commands.addSystems:
|
||||
mapAddSystems(data as CommandAddSystems);
|
||||
break;
|
||||
case Commands.updateSystems:
|
||||
mapUpdateSystems(data as CommandUpdateSystems);
|
||||
break;
|
||||
case Commands.removeSystems:
|
||||
removeSystems(data as CommandRemoveSystems);
|
||||
break;
|
||||
case Commands.addConnections:
|
||||
addConnections(data as CommandAddConnections);
|
||||
break;
|
||||
case Commands.removeConnections:
|
||||
removeConnections(data as CommandRemoveConnections);
|
||||
@@ -131,4 +131,20 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
useMapEventListener(event => {
|
||||
switch (event.name) {
|
||||
case Commands.addConnections:
|
||||
addConnections(event.data as CommandAddConnections);
|
||||
break;
|
||||
case Commands.addSystems:
|
||||
mapAddSystems(event.data as CommandAddSystems);
|
||||
break;
|
||||
case Commands.removeSystems:
|
||||
removeSystems(event.data as CommandRemoveSystems);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -79,7 +79,7 @@ export const SystemCustomLabelDialog = ({ systemId, visible, setVisible }: Syste
|
||||
|
||||
// @ts-ignore
|
||||
const handleInput = useCallback(e => {
|
||||
e.target.value = e.target.value.toUpperCase().replace(/[^A-Z0-9[\](){}]/g, '');
|
||||
e.target.value = e.target.value.toUpperCase().replace(/[^A-Z0-9\-[\](){}]/g, '');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
@@ -10,13 +10,17 @@ import {
|
||||
Setting,
|
||||
COSMIC_SIGNATURE,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureSettingsDialog';
|
||||
import { SignatureGroup } from '@/hooks/Mapper/types';
|
||||
|
||||
interface SystemLinkSignatureDialogProps {
|
||||
data: CommandLinkSignatureToSystem;
|
||||
setVisible: (visible: boolean) => void;
|
||||
}
|
||||
|
||||
const signatureSettings: Setting[] = [{ key: COSMIC_SIGNATURE, name: 'Show Cosmic Signatures', value: true }];
|
||||
const signatureSettings: Setting[] = [
|
||||
{ key: COSMIC_SIGNATURE, name: 'Show Cosmic Signatures', value: true },
|
||||
{ key: SignatureGroup.Wormhole, name: 'Wormhole', value: true },
|
||||
];
|
||||
|
||||
export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignatureDialogProps) => {
|
||||
const { outCommand } = useMapRootState();
|
||||
@@ -59,6 +63,7 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat
|
||||
>
|
||||
<SystemSignaturesContent
|
||||
systemId={`${data.solar_system_source}`}
|
||||
hideLinkedSignatures
|
||||
settings={signatureSettings}
|
||||
onSelect={handleSelect}
|
||||
selectable={true}
|
||||
|
||||
@@ -90,7 +90,7 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
|
||||
}, []);
|
||||
|
||||
const handleInput = useCallback((e: any) => {
|
||||
e.target.value = e.target.value.toUpperCase().replace(/[^A-Z0-9[\](){}]/g, '');
|
||||
e.target.value = e.target.value.toUpperCase().replace(/[^A-Z0-9\-[\](){}]/g, '');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
@@ -13,9 +13,10 @@ export const SystemInfoContent = ({ systemId }: SystemInfoContentProps) => {
|
||||
data: { systems, wormholesData },
|
||||
} = useMapRootState();
|
||||
|
||||
const sys = getSystemById(systems, systemId)!;
|
||||
const sys = getSystemById(systems, systemId)! || {};
|
||||
const { description } = sys;
|
||||
const { system_class, region_name, constellation_name, statics, effect_name, effect_power } = sys.system_static_info;
|
||||
const { system_class, region_name, constellation_name, statics, effect_name, effect_power } =
|
||||
sys.system_static_info || {};
|
||||
const isWH = isWormholeSpace(system_class);
|
||||
const sortedStatics = useMemo(() => sortWHClasses(wormholesData, statics), [wormholesData, statics]);
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
.verticalTabsContainer {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 300px;
|
||||
|
||||
:global {
|
||||
.p-tabview {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.p-tabview-panels {
|
||||
padding: 6px 1rem !important;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.p-tabview-nav-container {
|
||||
border-right: none;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.p-tabview-nav {
|
||||
flex-direction: column;
|
||||
width: 150px;
|
||||
min-height: 100%;
|
||||
border: none;
|
||||
|
||||
li {
|
||||
width: 100%;
|
||||
border-right: 4px solid var(--surface-hover);
|
||||
background-color: var(--surface-card);
|
||||
|
||||
transition:
|
||||
background-color 200ms,
|
||||
border-right-color 200ms;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--surface-hover);
|
||||
border-right: 4px solid var(--surface-100);
|
||||
}
|
||||
|
||||
.p-tabview-nav-link {
|
||||
transition: color 200ms;
|
||||
|
||||
justify-content: flex-end;
|
||||
padding: 10px;
|
||||
//background-color: var(--surface-card);
|
||||
background-color: initial;
|
||||
border: none;
|
||||
color: var(--gray-400);
|
||||
|
||||
border-radius: initial;
|
||||
font-weight: 400;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&.p-tabview-selected {
|
||||
background-color: var(--surface-50);
|
||||
border-right: 4px solid var(--primary-color);
|
||||
|
||||
.p-tabview-nav-link {
|
||||
font-weight: 600;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
//background-color: var(--surface-hover);
|
||||
border-right: 4px solid var(--primary-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.p-tabview-panel {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,10 @@ import { Dialog } from 'primereact/dialog';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Button } from 'primereact/button';
|
||||
import { Checkbox } from 'primereact/checkbox';
|
||||
import { TabPanel, TabView } from 'primereact/tabview';
|
||||
import styles from './SystemSignatureSettingsDialog.module.scss';
|
||||
|
||||
export type Setting = { key: string; name: string; value: boolean };
|
||||
export type Setting = { key: string; name: string; value: boolean; isFilter?: boolean };
|
||||
|
||||
export const COSMIC_SIGNATURE = 'Cosmic Signature';
|
||||
export const COSMIC_ANOMALY = 'Cosmic Anomaly';
|
||||
@@ -24,8 +26,12 @@ export const SystemSignatureSettingsDialog = ({
|
||||
onSave,
|
||||
onCancel,
|
||||
}: SystemSignatureSettingsDialogProps) => {
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
const [settings, setSettings] = useState<Setting[]>(defaultSettings);
|
||||
|
||||
const filterSettings = settings.filter(setting => setting.isFilter);
|
||||
const userSettings = settings.filter(setting => !setting.isFilter);
|
||||
|
||||
const handleSettingsChange = (key: string) => {
|
||||
setSettings(prevState => prevState.map(item => (item.key === key ? { ...item, value: !item.value } : item)));
|
||||
};
|
||||
@@ -35,23 +41,53 @@ export const SystemSignatureSettingsDialog = ({
|
||||
}, [onSave, settings]);
|
||||
|
||||
return (
|
||||
<Dialog header="Filter signatures" visible draggable={false} style={{ width: '300px' }} onHide={onCancel}>
|
||||
<Dialog header="System Signatures Settings" visible={true} onHide={onCancel} className="w-full max-w-lg">
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex flex-col gap-2">
|
||||
{settings.map(setting => {
|
||||
return (
|
||||
<div key={setting.key} className="flex items-center">
|
||||
<Checkbox
|
||||
inputId={setting.key}
|
||||
checked={setting.value}
|
||||
onChange={() => handleSettingsChange(setting.key)}
|
||||
/>
|
||||
<label htmlFor={setting.key} className="ml-2">
|
||||
{setting.name}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<div className={styles.verticalTabsContainer}>
|
||||
<TabView
|
||||
activeIndex={activeIndex}
|
||||
onTabChange={e => setActiveIndex(e.index)}
|
||||
className={styles.verticalTabView}
|
||||
>
|
||||
<TabPanel header="Filters" headerClassName={styles.verticalTabHeader}>
|
||||
<div className="w-full h-full flex flex-col gap-1">
|
||||
{filterSettings.map(setting => {
|
||||
return (
|
||||
<div key={setting.key} className="flex items-center">
|
||||
<Checkbox
|
||||
inputId={setting.key}
|
||||
checked={setting.value}
|
||||
onChange={() => handleSettingsChange(setting.key)}
|
||||
/>
|
||||
<label htmlFor={setting.key} className="ml-2">
|
||||
{setting.name}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel header="User Interface" headerClassName={styles.verticalTabHeader}>
|
||||
<div className="w-full h-full flex flex-col gap-1">
|
||||
{userSettings.map(setting => {
|
||||
return (
|
||||
<div key={setting.key} className="flex items-center">
|
||||
<Checkbox
|
||||
inputId={setting.key}
|
||||
checked={setting.value}
|
||||
onChange={() => handleSettingsChange(setting.key)}
|
||||
/>
|
||||
<label htmlFor={setting.key} className="ml-2">
|
||||
{setting.name}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</TabPanel>
|
||||
</TabView>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 justify-end">
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
SHIP,
|
||||
DRONE,
|
||||
} from './SystemSignatureSettingsDialog';
|
||||
import { SignatureGroup } from '@/hooks/Mapper/types';
|
||||
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
@@ -19,17 +20,25 @@ import { PrimeIcons } from 'primereact/api';
|
||||
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
|
||||
const settings: Setting[] = [
|
||||
{ key: COSMIC_ANOMALY, name: 'Show Anomalies', value: true },
|
||||
{ key: COSMIC_SIGNATURE, name: 'Show Cosmic Signatures', value: true },
|
||||
{ key: DEPLOYABLE, name: 'Show Deployables', value: true },
|
||||
{ key: STRUCTURE, name: 'Show Structures', value: true },
|
||||
{ key: STARBASE, name: 'Show Starbase', value: true },
|
||||
{ key: SHIP, name: 'Show Ships', value: true },
|
||||
{ key: DRONE, name: 'Show Drones And Charges', value: true },
|
||||
];
|
||||
const SIGNATURE_SETTINGS_KEY = 'wanderer_system_signature_settings_v3';
|
||||
export const SHOW_DESCRIPTION_COLUMN_SETTING = 'show_description_column_setting';
|
||||
|
||||
const SIGNATURE_SETTINGS_KEY = 'wanderer_system_signature_settings';
|
||||
const settings: Setting[] = [
|
||||
{ key: SHOW_DESCRIPTION_COLUMN_SETTING, name: 'Show Description Column', value: false, isFilter: false },
|
||||
{ key: COSMIC_ANOMALY, name: 'Show Anomalies', value: true, isFilter: true },
|
||||
{ key: COSMIC_SIGNATURE, name: 'Show Cosmic Signatures', value: true, isFilter: true },
|
||||
{ key: DEPLOYABLE, name: 'Show Deployables', value: true, isFilter: true },
|
||||
{ key: STRUCTURE, name: 'Show Structures', value: true, isFilter: true },
|
||||
{ key: STARBASE, name: 'Show Starbase', value: true, isFilter: true },
|
||||
{ key: SHIP, name: 'Show Ships', value: true, isFilter: true },
|
||||
{ key: DRONE, name: 'Show Drones And Charges', value: true, isFilter: true },
|
||||
{ key: SignatureGroup.Wormhole, name: 'Show Wormholes', value: true, isFilter: true },
|
||||
{ key: SignatureGroup.RelicSite, name: 'Show Relic Sites', value: true, isFilter: true },
|
||||
{ key: SignatureGroup.DataSite, name: 'Show Data Sites', value: true, isFilter: true },
|
||||
{ key: SignatureGroup.OreSite, name: 'Show Ore Sites', value: true, isFilter: true },
|
||||
{ key: SignatureGroup.GasSite, name: 'Show Gas Sites', value: true, isFilter: true },
|
||||
{ key: SignatureGroup.CombatSite, name: 'Show Combat Sites', value: true, isFilter: true },
|
||||
];
|
||||
|
||||
const defaultSettings = () => {
|
||||
return [...settings];
|
||||
@@ -91,8 +100,7 @@ export const SystemSignatures = () => {
|
||||
</InfoDrawer>
|
||||
<InfoDrawer title={<b className="text-slate-50">How to delete?</b>}>
|
||||
For delete any signature first of all you need select before
|
||||
<br /> and then use <b className="text-sky-500">Del</b> or{' '}
|
||||
<b className="text-sky-500">Backspace</b>
|
||||
<br /> and then use <b className="text-sky-500">Backspace</b>
|
||||
</InfoDrawer>
|
||||
</div>
|
||||
) as React.ReactNode,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useClipboard } from '@/hooks/Mapper/hooks/useClipboard';
|
||||
import { parseSignatures } from '@/hooks/Mapper/helpers';
|
||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { Commands, OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
import { WdTooltip, WdTooltipHandlers } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { GROUPS_LIST } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||
|
||||
import { DataTable, DataTableRowMouseEvent, SortOrder } from 'primereact/datatable';
|
||||
import { DataTable, DataTableRowClickEvent, DataTableRowMouseEvent, SortOrder } from 'primereact/datatable';
|
||||
import { Column } from 'primereact/column';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import useRefState from 'react-usestateref';
|
||||
@@ -21,14 +22,18 @@ import {
|
||||
getRowColorByTimeLeft,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/helpers';
|
||||
import {
|
||||
renderDescription,
|
||||
renderIcon,
|
||||
renderName,
|
||||
renderInfoColumn,
|
||||
renderTimeLeft,
|
||||
renderLinkedSystem,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders';
|
||||
// import { PrimeIcons } from 'primereact/api';
|
||||
import useLocalStorageState from 'use-local-storage-state';
|
||||
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { SignatureSettings } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings';
|
||||
import { useMapEventListener } from '@/hooks/Mapper/events';
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||
import { COSMIC_SIGNATURE } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureSettingsDialog';
|
||||
import { SHOW_DESCRIPTION_COLUMN_SETTING } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures';
|
||||
type SystemSignaturesSortSettings = {
|
||||
sortField: string;
|
||||
sortOrder: SortOrder;
|
||||
@@ -42,10 +47,17 @@ const SORT_DEFAULT_VALUES: SystemSignaturesSortSettings = {
|
||||
interface SystemSignaturesContentProps {
|
||||
systemId: string;
|
||||
settings: Setting[];
|
||||
hideLinkedSignatures?: boolean;
|
||||
selectable?: boolean;
|
||||
onSelect?: (signature: SystemSignature) => void;
|
||||
}
|
||||
export const SystemSignaturesContent = ({ systemId, settings, selectable, onSelect }: SystemSignaturesContentProps) => {
|
||||
export const SystemSignaturesContent = ({
|
||||
systemId,
|
||||
settings,
|
||||
hideLinkedSignatures,
|
||||
selectable,
|
||||
onSelect,
|
||||
}: SystemSignaturesContentProps) => {
|
||||
const { outCommand } = useMapRootState();
|
||||
|
||||
const [signatures, setSignatures, signaturesRef] = useRefState<SystemSignature[]>([]);
|
||||
@@ -53,6 +65,7 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele
|
||||
const [nameColumnWidth, setNameColumnWidth] = useState('auto');
|
||||
const [parsedSignatures, setParsedSignatures] = useState<SystemSignature[]>([]);
|
||||
const [askUser, setAskUser] = useState(false);
|
||||
const [selectedSignature, setSelectedSignature] = useState<SystemSignature | null>(null);
|
||||
|
||||
const [hoveredSig, setHoveredSig] = useState<SystemSignature | null>(null);
|
||||
|
||||
@@ -77,13 +90,36 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele
|
||||
}
|
||||
}, []);
|
||||
|
||||
const groupSettings = useMemo(() => settings.filter(s => (GROUPS_LIST as string[]).includes(s.key)), [settings]);
|
||||
const showDescriptionColumn = useMemo(
|
||||
() => settings.find(s => s.key === SHOW_DESCRIPTION_COLUMN_SETTING)?.value,
|
||||
[settings],
|
||||
);
|
||||
|
||||
const filteredSignatures = useMemo(() => {
|
||||
return signatures
|
||||
.filter(x => settings.find(y => y.key === x.kind)?.value)
|
||||
.filter(x => {
|
||||
if (hideLinkedSignatures && !!x.linked_system) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isCosmicSignature = x.kind === COSMIC_SIGNATURE;
|
||||
|
||||
if (isCosmicSignature) {
|
||||
const showCosmicSignatures = settings.find(y => y.key === COSMIC_SIGNATURE)?.value;
|
||||
if (showCosmicSignatures) {
|
||||
return !x.group || groupSettings.find(y => y.key === x.group)?.value;
|
||||
} else {
|
||||
return !!x.group && groupSettings.find(y => y.key === x.group)?.value;
|
||||
}
|
||||
}
|
||||
|
||||
return settings.find(y => y.key === x.kind)?.value;
|
||||
})
|
||||
.sort((a, b) => {
|
||||
return new Date(b.updated_at || 0).getTime() - new Date(a.updated_at || 0).getTime();
|
||||
});
|
||||
}, [signatures, settings]);
|
||||
}, [signatures, settings, groupSettings, hideLinkedSignatures]);
|
||||
|
||||
const handleGetSignatures = useCallback(async () => {
|
||||
const { signatures } = await outCommand({
|
||||
@@ -95,26 +131,6 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele
|
||||
setSignatures(signatures);
|
||||
}, [outCommand, systemId]);
|
||||
|
||||
// const updateSignatures = useCallback(
|
||||
// async (newSignatures: SystemSignature[], updateOnly: boolean) => {
|
||||
// const { added, updated, removed } = getActualSigs(signaturesRef.current, newSignatures, updateOnly);
|
||||
|
||||
// const { signatures: updatedSignatures } = await outCommand({
|
||||
// type: OutCommand.updateSignatures,
|
||||
// data: {
|
||||
// system_id: systemId,
|
||||
// added,
|
||||
// updated,
|
||||
// removed,
|
||||
// },
|
||||
// });
|
||||
|
||||
// setSignatures(() => updatedSignatures);
|
||||
// setSelectedSignatures([]);
|
||||
// },
|
||||
// [outCommand, systemId],
|
||||
// );
|
||||
|
||||
const handleUpdateSignatures = useCallback(
|
||||
async (newSignatures: SystemSignature[], updateOnly: boolean) => {
|
||||
const { added, updated, removed } = getActualSigs(signaturesRef.current, newSignatures, updateOnly);
|
||||
@@ -164,6 +180,8 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele
|
||||
}, [parsedSignatures, handleUpdateSignatures]);
|
||||
|
||||
const handleSelectSignatures = useCallback(
|
||||
// TODO still will be good to define types if we use typescript
|
||||
// @ts-ignore
|
||||
e => {
|
||||
if (selectable) {
|
||||
onSelect?.(e.value);
|
||||
@@ -176,7 +194,7 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele
|
||||
|
||||
useHotkey(true, ['a'], handleSelectAll);
|
||||
|
||||
useHotkey(false, ['Backspace', 'Delete'], handleDeleteSelected);
|
||||
useHotkey(false, ['Backspace'], handleDeleteSelected);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectable) {
|
||||
@@ -212,6 +230,18 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele
|
||||
handleGetSignatures();
|
||||
}, [systemId]);
|
||||
|
||||
useMapEventListener(event => {
|
||||
switch (event.name) {
|
||||
case Commands.signaturesUpdated:
|
||||
if (event.data?.toString() !== systemId.toString()) {
|
||||
return;
|
||||
}
|
||||
|
||||
handleGetSignatures();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new ResizeObserver(handleResize);
|
||||
if (tableRef.current) {
|
||||
@@ -240,13 +270,22 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele
|
||||
setHoveredSig(null);
|
||||
}, []);
|
||||
|
||||
// const renderToolbar = (/*row: SystemSignature*/) => {
|
||||
// return (
|
||||
// <div className="flex justify-end items-center gap-2">
|
||||
// <span className={clsx(PrimeIcons.PENCIL, 'text-[10px]')}></span>
|
||||
// </div>
|
||||
// );
|
||||
// };
|
||||
const renderToolbar = (/*row: SystemSignature*/) => {
|
||||
return (
|
||||
<div className="flex justify-end items-center gap-2 mr-[4px]">
|
||||
<WdTooltipWrapper content="To Edit Signature do double click">
|
||||
<span className={clsx(PrimeIcons.PENCIL, 'text-[10px]')}></span>
|
||||
</WdTooltipWrapper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const [showSignatureSettings, setShowSignatureSettings] = useState(false);
|
||||
|
||||
const handleRowClick = (e: DataTableRowClickEvent) => {
|
||||
setSelectedSignature(e.data as SystemSignature);
|
||||
setShowSignatureSettings(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -257,6 +296,7 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{/* @ts-ignore */}
|
||||
<DataTable
|
||||
className={classes.Table}
|
||||
value={filteredSignatures}
|
||||
@@ -268,6 +308,7 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele
|
||||
dataKey="eve_id"
|
||||
tableClassName="w-full select-none"
|
||||
resizableColumns={false}
|
||||
onRowDoubleClick={handleRowClick}
|
||||
rowHover
|
||||
selectAll
|
||||
sortField={sortSettings.sortField}
|
||||
@@ -291,7 +332,7 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele
|
||||
<Column
|
||||
bodyClassName="p-0 px-1"
|
||||
field="group"
|
||||
body={renderIcon}
|
||||
body={x => renderIcon(x)}
|
||||
style={{ maxWidth: 26, minWidth: 26, width: 26, height: 25 }}
|
||||
></Column>
|
||||
|
||||
@@ -310,41 +351,41 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele
|
||||
sortable
|
||||
></Column>
|
||||
<Column
|
||||
field="name"
|
||||
header="Name"
|
||||
field="info"
|
||||
// header="Info"
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
body={renderName}
|
||||
body={renderInfoColumn}
|
||||
style={{ maxWidth: nameColumnWidth }}
|
||||
hidden={compact || medium}
|
||||
sortable
|
||||
></Column>
|
||||
<Column
|
||||
field="linked_system"
|
||||
header="Leads To"
|
||||
headerClassName="whitespace-nowrap"
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
body={renderLinkedSystem}
|
||||
style={{ maxWidth: nameColumnWidth }}
|
||||
hidden={compact}
|
||||
sortable
|
||||
></Column>
|
||||
|
||||
{showDescriptionColumn && (
|
||||
<Column
|
||||
field="description"
|
||||
header="Description"
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
body={renderDescription}
|
||||
hidden={compact}
|
||||
sortable
|
||||
></Column>
|
||||
)}
|
||||
<Column
|
||||
field="updated_at"
|
||||
header="Updated"
|
||||
dataType="date"
|
||||
bodyClassName="w-[80px] text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
bodyClassName="w-[70px] text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
body={renderTimeLeft}
|
||||
sortable
|
||||
></Column>
|
||||
|
||||
{/*<Column*/}
|
||||
{/* bodyClassName="p-0 pl-1 pr-2"*/}
|
||||
{/* field="group"*/}
|
||||
{/* body={renderToolbar}*/}
|
||||
{/* headerClassName={headerClasses}*/}
|
||||
{/* style={{ maxWidth: 26, minWidth: 26, width: 26 }}*/}
|
||||
{/*></Column>*/}
|
||||
{!selectable && (
|
||||
<Column
|
||||
bodyClassName="p-0 pl-1 pr-2"
|
||||
field="group"
|
||||
body={renderToolbar}
|
||||
// headerClassName={headerClasses}
|
||||
style={{ maxWidth: 26, minWidth: 26, width: 26 }}
|
||||
></Column>
|
||||
)}
|
||||
</DataTable>
|
||||
</>
|
||||
)}
|
||||
@@ -353,6 +394,16 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele
|
||||
ref={tooltipRef}
|
||||
content={hoveredSig ? <SignatureView {...hoveredSig} /> : null}
|
||||
/>
|
||||
|
||||
{showSignatureSettings && (
|
||||
<SignatureSettings
|
||||
systemId={systemId}
|
||||
show
|
||||
onHide={() => setShowSignatureSettings(false)}
|
||||
signatureData={selectedSignature}
|
||||
/>
|
||||
)}
|
||||
|
||||
{askUser && (
|
||||
<div className="absolute left-[1px] top-[29px] h-[calc(100%-30px)] w-[calc(100%-3px)] bg-stone-900/10 backdrop-blur-sm">
|
||||
<div className="absolute top-0 left-0 w-full h-full flex flex-col items-center justify-center">
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
export * from './renderIcon';
|
||||
export * from './renderDescription';
|
||||
export * from './renderName';
|
||||
export * from './renderTimeLeft';
|
||||
export * from './renderLinkedSystem';
|
||||
export * from './renderInfoColumn';
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { SystemSignature } from '@/hooks/Mapper/types';
|
||||
|
||||
export const renderDescription = (row: SystemSignature) => {
|
||||
return <span title={row?.description}>{row?.description}</span>;
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { GroupType, SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { GROUPS } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||
|
||||
export const renderIcon = (row: SystemSignature) => {
|
||||
export const renderIcon = (row: SystemSignature, customSize?: Omit<GroupType, 'icon' | 'id'>) => {
|
||||
if (row.group == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -13,7 +13,7 @@ export const renderIcon = (row: SystemSignature) => {
|
||||
|
||||
return (
|
||||
<div className="flex justify-center items-center">
|
||||
<img src={group.icon} style={{ width: group.w, height: group.h }} />
|
||||
<img src={group.icon} style={{ width: customSize?.w ?? group.w, height: customSize?.h ?? group.h }} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.whFontSize {
|
||||
font-size: 11px !important;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { PrimeIcons } from 'primereact/api';
|
||||
import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { SystemViewStandalone, WHClassView } from '@/hooks/Mapper/components/ui-kit';
|
||||
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { renderName } from './renderName.tsx';
|
||||
import classes from './renderInfoColumn.module.scss';
|
||||
|
||||
export const renderInfoColumn = (row: SystemSignature) => {
|
||||
if (!row.group || row.group === SignatureGroup.Wormhole) {
|
||||
return (
|
||||
<div className="flex justify-start items-center gap-[6px]">
|
||||
{row.type && (
|
||||
<WHClassView
|
||||
className="text-[11px]"
|
||||
classNameWh={classes.whFontSize}
|
||||
highlightName
|
||||
hideWhClass={!!row.linked_system}
|
||||
whClassName={row.type}
|
||||
noOffset
|
||||
useShortTitle
|
||||
/>
|
||||
)}
|
||||
|
||||
{row.linked_system && (
|
||||
<>
|
||||
{/*<span className="w-4 h-4 hero-arrow-long-right"></span>*/}
|
||||
<span title={row.linked_system?.solar_system_name}>
|
||||
<SystemViewStandalone
|
||||
className={clsx('select-none text-center cursor-context-menu')}
|
||||
hideRegion
|
||||
{...row.linked_system}
|
||||
/>
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
{row.description && (
|
||||
<WdTooltipWrapper content={row.description}>
|
||||
<span className={clsx(PrimeIcons.EXCLAMATION_CIRCLE, 'text-[12px]')}></span>
|
||||
</WdTooltipWrapper>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex gap-1 items-center">
|
||||
{renderName(row)}{' '}
|
||||
{row.description && (
|
||||
<WdTooltipWrapper content={row.description}>
|
||||
<span className={clsx(PrimeIcons.EXCLAMATION_CIRCLE, 'text-[12px]')}></span>
|
||||
</WdTooltipWrapper>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,13 +1,20 @@
|
||||
import classes from './Connections.module.scss';
|
||||
import { Sidebar } from 'primereact/sidebar';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useMemo, useState, useCallback } from 'react';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { VirtualScroller, VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
|
||||
import clsx from 'clsx';
|
||||
import { ConnectionOutput, OutCommand, Passage, SolarSystemConnection } from '@/hooks/Mapper/types';
|
||||
import {
|
||||
ConnectionOutput,
|
||||
ConnectionInfoOutput,
|
||||
OutCommand,
|
||||
Passage,
|
||||
SolarSystemConnection,
|
||||
} from '@/hooks/Mapper/types';
|
||||
import { PassageCard } from './PassageCard';
|
||||
import { InfoDrawer, SystemView } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { kgToTons } from '@/hooks/Mapper/utils/kgToTons.ts';
|
||||
import { TimeAgo } from '@/hooks/Mapper/components/ui-kit';
|
||||
|
||||
const sortByDate = (a: string, b: string) => new Date(a).getTime() - new Date(b).getTime();
|
||||
|
||||
@@ -69,25 +76,44 @@ export const Connections = ({ selectedConnection, onHide }: OnTheMapProps) => {
|
||||
}, [connections, selectedConnection]);
|
||||
|
||||
const [passages, setPassages] = useState<Passage[]>([]);
|
||||
const [info, setInfo] = useState<ConnectionInfoOutput>(null);
|
||||
|
||||
const loadInfo = useCallback(
|
||||
async (connection: SolarSystemConnection) => {
|
||||
const result = await outCommand<ConnectionInfoOutput>({
|
||||
type: OutCommand.getConnectionInfo,
|
||||
data: {
|
||||
from: connection.source,
|
||||
to: connection.target,
|
||||
},
|
||||
});
|
||||
|
||||
setInfo(result);
|
||||
},
|
||||
[outCommand],
|
||||
);
|
||||
|
||||
const loadPassages = useCallback(
|
||||
async (connection: SolarSystemConnection) => {
|
||||
const result = await outCommand<ConnectionOutput>({
|
||||
type: OutCommand.getPassages,
|
||||
data: {
|
||||
from: connection.source,
|
||||
to: connection.target,
|
||||
},
|
||||
});
|
||||
|
||||
setPassages(result.passages.sort((a, b) => sortByDate(b.inserted_at, a.inserted_at)));
|
||||
},
|
||||
[outCommand],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedConnection) {
|
||||
return;
|
||||
}
|
||||
|
||||
const loadInfo = async () => {
|
||||
const result = await outCommand<ConnectionOutput>({
|
||||
type: OutCommand.getPassages,
|
||||
data: {
|
||||
from: selectedConnection.source,
|
||||
to: selectedConnection.target,
|
||||
},
|
||||
});
|
||||
|
||||
setPassages(result.passages.sort((a, b) => sortByDate(b.inserted_at, a.inserted_at)));
|
||||
};
|
||||
|
||||
loadInfo();
|
||||
loadInfo(selectedConnection);
|
||||
loadPassages(selectedConnection);
|
||||
}, [selectedConnection]);
|
||||
|
||||
const approximateMass = useMemo(() => {
|
||||
@@ -132,6 +158,10 @@ export const Connections = ({ selectedConnection, onHide }: OnTheMapProps) => {
|
||||
{kgToTons(approximateMass)}
|
||||
</InfoDrawer>
|
||||
|
||||
<InfoDrawer title="Mark EOL Time" rightSide>
|
||||
{info?.marl_eol_time ? <TimeAgo timestamp={info.marl_eol_time} /> : ' unknown '}
|
||||
</InfoDrawer>
|
||||
|
||||
<div className="flex gap-2"></div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -9,21 +9,25 @@ import { OutCommand } from '@/hooks/Mapper/types';
|
||||
export enum UserSettingsRemoteProps {
|
||||
link_signature_on_splash = 'link_signature_on_splash',
|
||||
select_on_spash = 'select_on_spash',
|
||||
delete_connection_with_sigs = 'delete_connection_with_sigs',
|
||||
}
|
||||
|
||||
export const DEFAULT_REMOTE_SETTINGS = {
|
||||
[UserSettingsRemoteProps.link_signature_on_splash]: false,
|
||||
[UserSettingsRemoteProps.select_on_spash]: false,
|
||||
[UserSettingsRemoteProps.delete_connection_with_sigs]: false,
|
||||
};
|
||||
|
||||
export const UserSettingsRemoteList = [
|
||||
UserSettingsRemoteProps.link_signature_on_splash,
|
||||
UserSettingsRemoteProps.select_on_spash,
|
||||
UserSettingsRemoteProps.delete_connection_with_sigs,
|
||||
];
|
||||
|
||||
export type UserSettingsRemote = {
|
||||
link_signature_on_splash: boolean;
|
||||
select_on_spash: boolean;
|
||||
delete_connection_with_sigs: boolean;
|
||||
};
|
||||
|
||||
export type UserSettings = UserSettingsRemote & InterfaceStoredSettings;
|
||||
@@ -51,6 +55,10 @@ const SIGNATURES_CHECKBOXES_PROPS: CheckboxesList = [
|
||||
{ prop: UserSettingsRemoteProps.link_signature_on_splash, label: 'Link signature on splash' },
|
||||
];
|
||||
|
||||
const CONNECTIONS_CHECKBOXES_PROPS: CheckboxesList = [
|
||||
{ prop: UserSettingsRemoteProps.delete_connection_with_sigs, label: 'Delete connections to linked signatures' },
|
||||
];
|
||||
|
||||
const UI_CHECKBOXES_PROPS: CheckboxesList = [
|
||||
{ prop: InterfaceStoredSettingsProps.isShowMenu, label: 'Enable compact map menu bar' },
|
||||
];
|
||||
@@ -149,8 +157,8 @@ export const MapSettings = ({ show, onHide }: MapSettingsProps) => {
|
||||
{renderCheckboxesList(SYSTEMS_CHECKBOXES_PROPS)}
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel disabled header="Connections" headerClassName={styles.verticalTabHeader}>
|
||||
<p>Connections</p>
|
||||
<TabPanel header="Connections" headerClassName={styles.verticalTabHeader}>
|
||||
{renderCheckboxesList(CONNECTIONS_CHECKBOXES_PROPS)}
|
||||
</TabPanel>
|
||||
<TabPanel header="Signatures" headerClassName={styles.verticalTabHeader}>
|
||||
{renderCheckboxesList(SIGNATURES_CHECKBOXES_PROPS)}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { createGenericContext } from '@/hooks/Mapper/utils/abstractContextProvider.tsx';
|
||||
|
||||
export interface SystemsSettingsProvider {
|
||||
systemId: string;
|
||||
}
|
||||
|
||||
const { Provider, useContextValue } = createGenericContext<SystemsSettingsProvider>();
|
||||
|
||||
export const SystemsSettingsProvider = Provider;
|
||||
export const useSystemsSettingsProvider = useContextValue;
|
||||
@@ -0,0 +1,81 @@
|
||||
.verticalTabsContainer {
|
||||
width: 100%;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.verticalTabsContainer {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 300px;
|
||||
|
||||
:global {
|
||||
.p-tabview {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.p-tabview-panels {
|
||||
padding: 6px 1rem !important;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.p-tabview-nav-container {
|
||||
border-right: none;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.p-tabview-nav {
|
||||
flex-direction: column;
|
||||
width: 150px;
|
||||
min-height: 100%;
|
||||
border: none;
|
||||
|
||||
li {
|
||||
width: 100%;
|
||||
border-right: 4px solid var(--surface-hover);
|
||||
background-color: var(--surface-card);
|
||||
|
||||
transition: background-color 200ms, border-right-color 200ms;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--surface-hover);
|
||||
border-right: 4px solid var(--surface-100);
|
||||
}
|
||||
|
||||
.p-tabview-nav-link {
|
||||
transition: color 200ms;
|
||||
|
||||
justify-content: flex-end;
|
||||
padding: 10px;
|
||||
background-color: initial;
|
||||
border: none;
|
||||
color: var(--gray-400);
|
||||
|
||||
border-radius: initial;
|
||||
font-weight: 400;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&.p-tabview-selected {
|
||||
background-color: var(--surface-50);
|
||||
border-right: 4px solid var(--primary-color);
|
||||
|
||||
.p-tabview-nav-link {
|
||||
font-weight: 600;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-right: 4px solid var(--primary-color);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.p-tabview-panel {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
// import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { OutCommand, SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { Controller, FormProvider, useForm } from 'react-hook-form';
|
||||
import {
|
||||
SignatureGroupContent,
|
||||
SignatureGroupSelect,
|
||||
} from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components';
|
||||
import { InputText } from 'primereact/inputtext';
|
||||
import { SystemsSettingsProvider } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/Provider.tsx';
|
||||
import { Button } from 'primereact/button';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
|
||||
type SystemSignaturePrepared = Omit<SystemSignature, 'linked_system'> & { linked_system: string };
|
||||
|
||||
export interface MapSettingsProps {
|
||||
systemId: string;
|
||||
show: boolean;
|
||||
onHide: () => void;
|
||||
signatureData: SystemSignature | null;
|
||||
}
|
||||
|
||||
export const SignatureSettings = ({ systemId, show, onHide, signatureData }: MapSettingsProps) => {
|
||||
const { outCommand } = useMapRootState();
|
||||
|
||||
const handleShow = async () => {};
|
||||
const form = useForm<Partial<SystemSignaturePrepared>>({});
|
||||
|
||||
const handleSave = useCallback(async () => {
|
||||
if (!signatureData) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { group, ...values } = form.getValues();
|
||||
let out = { ...signatureData };
|
||||
|
||||
switch (group) {
|
||||
case SignatureGroup.Wormhole:
|
||||
if (values.linked_system) {
|
||||
await outCommand({
|
||||
type: OutCommand.linkSignatureToSystem,
|
||||
data: {
|
||||
signature_eve_id: signatureData.eve_id,
|
||||
solar_system_source: systemId,
|
||||
solar_system_target: values.linked_system,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (values.type != null) {
|
||||
out = { ...out, type: values.type };
|
||||
}
|
||||
|
||||
if (signatureData.group !== SignatureGroup.Wormhole) {
|
||||
out = { ...out, name: '' };
|
||||
}
|
||||
|
||||
break;
|
||||
case SignatureGroup.CosmicSignature:
|
||||
out = { ...out, type: '', name: '' };
|
||||
break;
|
||||
default:
|
||||
if (values.name != null) {
|
||||
out = { ...out, name: values.name ?? '' };
|
||||
}
|
||||
}
|
||||
|
||||
if (values.description != null) {
|
||||
out = { ...out, description: values.description };
|
||||
}
|
||||
|
||||
// Note: when type of signature changed from WH to other type - we should drop name
|
||||
if (
|
||||
group !== SignatureGroup.Wormhole && // new
|
||||
signatureData.group === SignatureGroup.Wormhole && // prev
|
||||
signatureData.linked_system
|
||||
) {
|
||||
await outCommand({
|
||||
type: OutCommand.unlinkSignature,
|
||||
data: { signature_eve_id: signatureData.eve_id, solar_system_source: systemId },
|
||||
});
|
||||
|
||||
out = { ...out, type: '' };
|
||||
}
|
||||
|
||||
if (group === SignatureGroup.Wormhole && signatureData.linked_system != null && values.linked_system === null) {
|
||||
await outCommand({
|
||||
type: OutCommand.unlinkSignature,
|
||||
data: { signature_eve_id: signatureData.eve_id, solar_system_source: systemId },
|
||||
});
|
||||
}
|
||||
|
||||
// Note: despite groups have optional type - this will always set
|
||||
out = { ...out, group: group! };
|
||||
|
||||
await outCommand({
|
||||
type: OutCommand.updateSignatures,
|
||||
data: {
|
||||
system_id: systemId,
|
||||
added: [],
|
||||
updated: [out],
|
||||
removed: [],
|
||||
},
|
||||
});
|
||||
|
||||
form.reset();
|
||||
onHide();
|
||||
}, [form, onHide, outCommand, signatureData, systemId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!signatureData) {
|
||||
form.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
const { linked_system, ...rest } = signatureData;
|
||||
|
||||
form.reset({
|
||||
linked_system: linked_system?.solar_system_id.toString() ?? undefined,
|
||||
...rest,
|
||||
});
|
||||
}, [form, signatureData]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
header={`Signature Edit [${signatureData?.eve_id}]`}
|
||||
visible={show}
|
||||
draggable={false}
|
||||
style={{ width: '390px' }}
|
||||
onShow={handleShow}
|
||||
onHide={() => {
|
||||
if (!show) {
|
||||
return;
|
||||
}
|
||||
|
||||
onHide();
|
||||
}}
|
||||
>
|
||||
<SystemsSettingsProvider initialValue={{ systemId }}>
|
||||
<FormProvider {...form}>
|
||||
<div className="flex flex-col gap-2 justify-between">
|
||||
<div className="w-full flex flex-col gap-1 p-1 min-h-[150px]">
|
||||
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
|
||||
<span>Group:</span>
|
||||
<SignatureGroupSelect name="group" />
|
||||
</label>
|
||||
|
||||
<SignatureGroupContent />
|
||||
|
||||
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
|
||||
<span>Description:</span>
|
||||
<Controller
|
||||
name="description"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<InputText placeholder="Type description" value={field.value} onChange={field.onChange} />
|
||||
)}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 justify-end">
|
||||
<Button onClick={handleSave} outlined size="small" label="Save"></Button>
|
||||
</div>
|
||||
</div>
|
||||
</FormProvider>
|
||||
</SystemsSettingsProvider>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { useSystemsSettingsProvider } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/Provider.tsx';
|
||||
import { SignatureGroupContentWormholes } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureGroupContentWormholes.tsx';
|
||||
import { InputText } from 'primereact/inputtext';
|
||||
|
||||
export interface SignatureGroupContentProps {}
|
||||
|
||||
export const SignatureGroupContent = ({}: SignatureGroupContentProps) => {
|
||||
const { watch, control } = useFormContext<SystemSignature>();
|
||||
const group = watch('group');
|
||||
|
||||
const {
|
||||
value: { systemId },
|
||||
} = useSystemsSettingsProvider();
|
||||
|
||||
if (!systemId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (group === SignatureGroup.Wormhole) {
|
||||
return (
|
||||
<>
|
||||
<SignatureGroupContentWormholes />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (group === SignatureGroup.CosmicSignature) {
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
|
||||
<span>Name:</span>
|
||||
<Controller
|
||||
name="name"
|
||||
control={control}
|
||||
render={({ field }) => <InputText placeholder="Name" value={field.value} onChange={field.onChange} />}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './SignatureGroupContent';
|
||||
@@ -0,0 +1,18 @@
|
||||
import { SignatureWormholeTypeSelect } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureWormholeTypeSelect';
|
||||
import { SignatureLeadsToSelect } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureLeadsToSelect';
|
||||
|
||||
export const SignatureGroupContentWormholes = () => {
|
||||
return (
|
||||
<>
|
||||
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
|
||||
<span>Type:</span>
|
||||
<SignatureWormholeTypeSelect name="type" />
|
||||
</label>
|
||||
|
||||
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
|
||||
<span>Leads To:</span>
|
||||
<SignatureLeadsToSelect name="linked_system" />
|
||||
</label>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
import { Dropdown } from 'primereact/dropdown';
|
||||
import clsx from 'clsx';
|
||||
import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { renderIcon } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
|
||||
const signatureGroupOptions = Object.keys(SignatureGroup).map(x => ({
|
||||
value: SignatureGroup[x as keyof typeof SignatureGroup],
|
||||
label: SignatureGroup[x as keyof typeof SignatureGroup],
|
||||
}));
|
||||
|
||||
// @ts-ignore
|
||||
const renderSignatureTemplate = option => {
|
||||
if (!option) {
|
||||
return 'No group selected';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex gap-2 items-center">
|
||||
<span className="w-[20px] mt-[1px] flex justify-center items-center">
|
||||
{renderIcon(
|
||||
{ group: option.label } as SystemSignature,
|
||||
option.label === SignatureGroup.CosmicSignature ? { w: 10, h: 10 } : { w: 16, h: 16 },
|
||||
)}
|
||||
</span>
|
||||
<span>{option.label}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export interface SignatureGroupSelectProps {
|
||||
name: string;
|
||||
defaultValue?: string;
|
||||
}
|
||||
|
||||
export const SignatureGroupSelect = ({ name, defaultValue = '' }: SignatureGroupSelectProps) => {
|
||||
const { control } = useFormContext();
|
||||
return (
|
||||
<Controller
|
||||
name={name}
|
||||
control={control}
|
||||
defaultValue={defaultValue}
|
||||
render={({ field }) => (
|
||||
<Dropdown
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
options={signatureGroupOptions}
|
||||
optionLabel="label"
|
||||
optionValue="value"
|
||||
placeholder="Select group"
|
||||
className={clsx('w-full')}
|
||||
scrollHeight="240px"
|
||||
itemTemplate={renderSignatureTemplate}
|
||||
valueTemplate={renderSignatureTemplate}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './SignatureGroupSelect';
|
||||
@@ -0,0 +1,3 @@
|
||||
.SystemView {
|
||||
font-size: 14px !important;
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
import { Dropdown } from 'primereact/dropdown';
|
||||
import clsx from 'clsx';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { useSystemsSettingsProvider } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/Provider.tsx';
|
||||
import { useSystemInfo } from '@/hooks/Mapper/components/hooks';
|
||||
import { useMemo } from 'react';
|
||||
import { SystemView } from '@/hooks/Mapper/components/ui-kit';
|
||||
import classes from './SignatureLeadsToSelect.module.scss';
|
||||
import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts';
|
||||
import { SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { WORMHOLES_ADDITIONAL_INFO_BY_CLASS_ID } from '@/hooks/Mapper/components/map/constants.ts';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
|
||||
// @ts-ignore
|
||||
const renderLinkedSystemItem = (option: { value: string }) => {
|
||||
if (option.value == null) {
|
||||
return <div className="flex gap-2 items-center">No linked system</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex gap-2 items-center">
|
||||
<SystemView systemId={option.value} className={classes.SystemView} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
const renderLinkedSystemValue = (option: { value: string }) => {
|
||||
if (!option) {
|
||||
return 'Select Leads To system';
|
||||
}
|
||||
|
||||
if (option.value == null) {
|
||||
return 'Select Leads To system';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex gap-2 items-center">
|
||||
<SystemView systemId={option.value} className={classes.SystemView} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderLeadsToEmpty = () => <div className="flex items-center text-[14px]">No wormhole to select</div>;
|
||||
|
||||
export interface SignatureLeadsToSelectProps {
|
||||
name: string;
|
||||
defaultValue?: string;
|
||||
}
|
||||
|
||||
export const SignatureLeadsToSelect = ({ name, defaultValue = '' }: SignatureLeadsToSelectProps) => {
|
||||
const { control, watch } = useFormContext<SystemSignature>();
|
||||
const group = watch('type');
|
||||
|
||||
const {
|
||||
value: { systemId },
|
||||
} = useSystemsSettingsProvider();
|
||||
|
||||
const { leadsTo } = useSystemInfo({ systemId });
|
||||
const { systems: systemStatics } = useLoadSystemStatic({ systems: leadsTo });
|
||||
const {
|
||||
data: { wormholes },
|
||||
} = useMapRootState();
|
||||
|
||||
const leadsToOptions = useMemo(() => {
|
||||
return [
|
||||
{ value: null },
|
||||
...leadsTo
|
||||
.filter(systemId => {
|
||||
const systemStatic = systemStatics.get(parseInt(systemId));
|
||||
const whInfo = wormholes.find(x => x.name === group);
|
||||
|
||||
if (!systemStatic || !whInfo || group === 'K162') {
|
||||
return true;
|
||||
}
|
||||
|
||||
const { id: whType } = WORMHOLES_ADDITIONAL_INFO_BY_CLASS_ID[systemStatic.system_class];
|
||||
return whInfo.dest === whType;
|
||||
})
|
||||
.map(x => ({ value: x })),
|
||||
];
|
||||
}, [group, leadsTo, systemStatics, wormholes]);
|
||||
|
||||
return (
|
||||
<Controller
|
||||
// @ts-ignore
|
||||
name={name}
|
||||
control={control}
|
||||
defaultValue={defaultValue}
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Dropdown
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
options={leadsToOptions}
|
||||
optionValue="value"
|
||||
placeholder="Select Leads To wormhole"
|
||||
className={clsx('w-full')}
|
||||
scrollHeight="240px"
|
||||
itemTemplate={renderLinkedSystemItem}
|
||||
valueTemplate={renderLinkedSystemValue}
|
||||
emptyMessage={renderLeadsToEmpty}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './SignatureLeadsToSelect.tsx';
|
||||
@@ -0,0 +1,134 @@
|
||||
import { Dropdown } from 'primereact/dropdown';
|
||||
import clsx from 'clsx';
|
||||
import { Respawn, SolarSystemStaticInfoRaw, WormholeDataRaw } from '@/hooks/Mapper/types';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useSystemsSettingsProvider } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/Provider.tsx';
|
||||
import { useSystemInfo } from '@/hooks/Mapper/components/hooks';
|
||||
import {
|
||||
SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS,
|
||||
WORMHOLES_ADDITIONAL_INFO_BY_CLASS_ID,
|
||||
} from '@/hooks/Mapper/components/map/constants.ts';
|
||||
import { useMemo } from 'react';
|
||||
import { WHClassView } from '@/hooks/Mapper/components/ui-kit';
|
||||
|
||||
const getPossibleWormholes = (systemStatic: SolarSystemStaticInfoRaw, wormholes: WormholeDataRaw[]) => {
|
||||
const { id: whType } = WORMHOLES_ADDITIONAL_INFO_BY_CLASS_ID[systemStatic.system_class];
|
||||
|
||||
// @ts-ignore
|
||||
const spawnClassGroup = SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS[whType];
|
||||
const possibleWHTypes = wormholes.filter(x => x.src.includes(spawnClassGroup));
|
||||
|
||||
return {
|
||||
statics: possibleWHTypes
|
||||
.filter(x => x.respawn.some(y => y === Respawn.static))
|
||||
.filter(x => systemStatic.statics.includes(x.name)),
|
||||
k162: wormholes.find(x => x.name === 'K162')!,
|
||||
wanderings: possibleWHTypes.filter(x => x.respawn.some(y => y === Respawn.wandering)),
|
||||
};
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
const renderWHTypeGroupTemplate = option => {
|
||||
return (
|
||||
<div className="flex gap-2 items-center">
|
||||
<span>{option.label}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
const renderWHTypeTemplateValue = (option: { label: string; data: WormholeDataRaw }) => {
|
||||
if (!option) {
|
||||
return 'Select wormhole type';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex gap-2 items-center">
|
||||
<WHClassView whClassName={option.data.name} noOffset useShortTitle />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
const renderWHTypeTemplate = (option: { label: string; data: WormholeDataRaw }) => {
|
||||
return (
|
||||
<div className="flex gap-2 items-center ml-[1rem]">
|
||||
<WHClassView whClassName={option.data.name} noOffset useShortTitle />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export interface SignatureGroupSelectProps {
|
||||
name: string;
|
||||
defaultValue?: string;
|
||||
}
|
||||
|
||||
export const SignatureWormholeTypeSelect = ({ name, defaultValue = '' }: SignatureGroupSelectProps) => {
|
||||
const { control } = useFormContext();
|
||||
|
||||
const {
|
||||
data: { wormholes },
|
||||
} = useMapRootState();
|
||||
|
||||
const {
|
||||
value: { systemId },
|
||||
} = useSystemsSettingsProvider();
|
||||
|
||||
const system = useSystemInfo({ systemId });
|
||||
|
||||
const possibleWormholesOptions = useMemo(() => {
|
||||
const possibleWormholes = getPossibleWormholes(system.staticInfo, wormholes);
|
||||
|
||||
return [
|
||||
{
|
||||
label: 'Statics',
|
||||
items: [
|
||||
...possibleWormholes.statics.map(x => ({
|
||||
label: x.name,
|
||||
value: x.name,
|
||||
data: x,
|
||||
})),
|
||||
{
|
||||
value: possibleWormholes.k162.name,
|
||||
label: possibleWormholes.k162.name,
|
||||
data: possibleWormholes.k162,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Wanderings',
|
||||
items: possibleWormholes.wanderings.map(x => ({
|
||||
label: x.name,
|
||||
value: x.name,
|
||||
data: x,
|
||||
})),
|
||||
},
|
||||
];
|
||||
}, [system, wormholes]);
|
||||
|
||||
return (
|
||||
<Controller
|
||||
name={name}
|
||||
control={control}
|
||||
defaultValue={defaultValue}
|
||||
render={({ field }) => (
|
||||
<Dropdown
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
options={possibleWormholesOptions}
|
||||
optionLabel="label"
|
||||
optionValue="value"
|
||||
placeholder="Select wormhole type"
|
||||
optionGroupLabel="label"
|
||||
optionGroupChildren="items"
|
||||
className={clsx('w-full')}
|
||||
scrollHeight="240px"
|
||||
optionGroupTemplate={renderWHTypeGroupTemplate}
|
||||
itemTemplate={renderWHTypeTemplate}
|
||||
valueTemplate={renderWHTypeTemplateValue}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './SignatureWormholeTypeSelect';
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './SignatureGroupSelect';
|
||||
export * from './SignatureGroupContent';
|
||||
@@ -0,0 +1 @@
|
||||
export * from './SignatureSettings.tsx';
|
||||
@@ -5,6 +5,11 @@
|
||||
.WHClassViewContent {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
|
||||
&.NoOffset {
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.WHClassName {
|
||||
@@ -13,3 +18,12 @@
|
||||
font-weight: bold;
|
||||
top: -2px;
|
||||
}
|
||||
|
||||
.NoOffset {
|
||||
*.WHClassName {
|
||||
position: relative;
|
||||
font-size: 12px;
|
||||
font-weight: initial !important;
|
||||
top: initial !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,26 +16,42 @@ const prepareMass = (mass: number) => {
|
||||
|
||||
export interface WHClassViewProps {
|
||||
whClassName: string;
|
||||
noOffset?: boolean;
|
||||
useShortTitle?: boolean;
|
||||
hideWhClass?: boolean;
|
||||
highlightName?: boolean;
|
||||
className?: string;
|
||||
classNameWh?: string;
|
||||
}
|
||||
|
||||
export const WHClassView = ({ whClassName }: WHClassViewProps) => {
|
||||
export const WHClassView = ({
|
||||
whClassName,
|
||||
noOffset,
|
||||
useShortTitle,
|
||||
hideWhClass,
|
||||
highlightName,
|
||||
className,
|
||||
classNameWh,
|
||||
}: WHClassViewProps) => {
|
||||
const {
|
||||
data: { wormholesData },
|
||||
} = useMapRootState();
|
||||
|
||||
const whData = useMemo(() => wormholesData[whClassName], [whClassName, wormholesData]);
|
||||
const whClass = useMemo(() => WORMHOLES_ADDITIONAL_INFO[whData.dest], [whData.dest]);
|
||||
const whClassStyle = WORMHOLE_CLASS_STYLES[whClass.wormholeClassID];
|
||||
const whClassStyle = WORMHOLE_CLASS_STYLES[whClass?.wormholeClassID] ?? '';
|
||||
|
||||
const uid = useMemo(() => new Date().getTime().toString(), []);
|
||||
|
||||
return (
|
||||
<div className={classes.WHClassViewRoot}>
|
||||
<div className={clsx(classes.WHClassViewRoot, className)}>
|
||||
<Tooltip
|
||||
target={`.wh-name${whClassName}`}
|
||||
target={`.wh-name${whClassName}${uid}`}
|
||||
position="right"
|
||||
mouseTrack
|
||||
mouseTrackLeft={20}
|
||||
mouseTrackTop={30}
|
||||
className="border border-green-300 rounded border-opacity-10 bg-stone-900 bg-opacity-70 "
|
||||
className="border border-green-300 rounded border-opacity-10 bg-stone-900 bg-opacity-90 "
|
||||
>
|
||||
<div className="flex gap-3">
|
||||
<div className="flex flex-col gap-1">
|
||||
@@ -49,9 +65,20 @@ export const WHClassView = ({ whClassName }: WHClassViewProps) => {
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
<div className={clsx(classes.WHClassViewContent, 'wh-name select-none cursor-help', `wh-name${whClassName}`)}>
|
||||
<span>{whClassName}</span>
|
||||
<span className={clsx(classes.WHClassName, whClassStyle)}>{whClass.shortName}</span>
|
||||
<div
|
||||
className={clsx(
|
||||
classes.WHClassViewContent,
|
||||
{ [classes.NoOffset]: noOffset },
|
||||
'wh-name select-none cursor-help',
|
||||
`wh-name${whClassName}${uid}`,
|
||||
)}
|
||||
>
|
||||
<span className={clsx({ [whClassStyle]: highlightName })}>{whClassName}</span>
|
||||
{!hideWhClass && whClass && (
|
||||
<span className={clsx(classes.WHClassName, whClassStyle, classNameWh)}>
|
||||
{useShortTitle ? whClass.shortTitle : whClass.shortName}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { createEvent } from 'react-event-hook';
|
||||
|
||||
export interface MapEvent {
|
||||
name: string;
|
||||
data: {
|
||||
solar_system_source: number;
|
||||
solar_system_target: number;
|
||||
};
|
||||
import { Command, CommandData } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||
|
||||
export interface MapEvent<T extends Command> {
|
||||
name: T;
|
||||
data: CommandData[T];
|
||||
}
|
||||
|
||||
const { useMapEventListener, emitMapEvent } = createEvent('map-event')<MapEvent>();
|
||||
const { useMapEventListener, emitMapEvent } = createEvent('map-event')<MapEvent<Command>>();
|
||||
|
||||
export { useMapEventListener, emitMapEvent };
|
||||
|
||||
@@ -19,6 +19,7 @@ export const parseSignatures = (value: string, availableKeys: string[]): SystemS
|
||||
kind: availableKeys.includes(sigArrInfo[1]) ? sigArrInfo[1] : COSMIC_SIGNATURE,
|
||||
group: sigArrInfo[2],
|
||||
name: sigArrInfo[3],
|
||||
type: '',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@ import { WORMHOLES_ADDITIONAL_INFO } from '@/hooks/Mapper/components/map/constan
|
||||
import { WormholeDataRaw } from '@/hooks/Mapper/types';
|
||||
|
||||
export const sortWHClasses = (wormholesData: Record<string, WormholeDataRaw>, statics: string[]) => {
|
||||
if (!statics) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return statics
|
||||
.map(x => wormholesData[x])
|
||||
.map(x => ({ name: x.name, ...WORMHOLES_ADDITIONAL_INFO[x.dest] }))
|
||||
|
||||
@@ -12,6 +12,7 @@ export type MapRootData = MapUnionTypes & {
|
||||
|
||||
const INITIAL_DATA: MapRootData = {
|
||||
wormholesData: {},
|
||||
wormholes: [],
|
||||
effects: {},
|
||||
characters: [],
|
||||
userCharacters: [],
|
||||
|
||||
@@ -24,6 +24,7 @@ export const useMapInit = () => {
|
||||
|
||||
if (wormholes) {
|
||||
updateData.wormholesData = wormholes.reduce((acc, x) => ({ ...acc, [x.name]: x }), {});
|
||||
updateData.wormholes = wormholes;
|
||||
}
|
||||
|
||||
if (effects) {
|
||||
|
||||
@@ -49,15 +49,25 @@ export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
|
||||
break;
|
||||
case Commands.addSystems:
|
||||
addSystems(data as CommandAddSystems);
|
||||
setTimeout(() => {
|
||||
emitMapEvent({ name: Commands.addSystems, data });
|
||||
}, 100);
|
||||
break;
|
||||
case Commands.updateSystems:
|
||||
updateSystems(data as CommandUpdateSystems);
|
||||
break;
|
||||
case Commands.removeSystems:
|
||||
removeSystems(data as CommandRemoveSystems);
|
||||
setTimeout(() => {
|
||||
emitMapEvent({ name: Commands.removeSystems, data });
|
||||
}, 100);
|
||||
|
||||
break;
|
||||
case Commands.addConnections:
|
||||
addConnections(data as CommandAddConnections);
|
||||
setTimeout(() => {
|
||||
emitMapEvent({ name: Commands.addConnections, data });
|
||||
}, 100);
|
||||
break;
|
||||
case Commands.removeConnections:
|
||||
removeConnections(data as CommandRemoveConnections);
|
||||
@@ -96,6 +106,8 @@ export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
|
||||
break;
|
||||
|
||||
case Commands.linkSignatureToSystem:
|
||||
// TODO command data type lost
|
||||
// @ts-ignore
|
||||
emitMapEvent({ name: Commands.linkSignatureToSystem, data });
|
||||
break;
|
||||
|
||||
@@ -103,6 +115,12 @@ export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
|
||||
// do nothing here
|
||||
break;
|
||||
|
||||
case Commands.signaturesUpdated:
|
||||
// TODO command data type lost
|
||||
// @ts-ignore
|
||||
emitMapEvent({ name: Commands.signaturesUpdated, data });
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn(`JOipP Interface handlers: Unknown command: ${type}`, data);
|
||||
break;
|
||||
|
||||
@@ -11,6 +11,10 @@ export type Passage = {
|
||||
character: PassageLimitedCharacterType;
|
||||
};
|
||||
|
||||
export type ConnectionInfoOutput = {
|
||||
marl_eol_time: string;
|
||||
};
|
||||
|
||||
export type ConnectionOutput = {
|
||||
passages: Passage[];
|
||||
};
|
||||
|
||||
@@ -24,6 +24,7 @@ export enum Commands {
|
||||
centerSystem = 'center_system',
|
||||
selectSystem = 'select_system',
|
||||
linkSignatureToSystem = 'link_signature_to_system',
|
||||
signaturesUpdated = 'signatures_updated',
|
||||
}
|
||||
|
||||
export type Command =
|
||||
@@ -44,7 +45,8 @@ export type Command =
|
||||
| Commands.routes
|
||||
| Commands.selectSystem
|
||||
| Commands.centerSystem
|
||||
| Commands.linkSignatureToSystem;
|
||||
| Commands.linkSignatureToSystem
|
||||
| Commands.signaturesUpdated;
|
||||
|
||||
export type CommandInit = {
|
||||
systems: SolarSystemRawType[];
|
||||
@@ -81,6 +83,7 @@ export type CommandLinkSignatureToSystem = {
|
||||
solar_system_source: number;
|
||||
solar_system_target: number;
|
||||
};
|
||||
export type CommandLinkSignaturesUpdated = number;
|
||||
|
||||
export interface CommandData {
|
||||
[Commands.init]: CommandInit;
|
||||
@@ -101,6 +104,7 @@ export interface CommandData {
|
||||
[Commands.selectSystem]: CommandSelectSystem;
|
||||
[Commands.centerSystem]: CommandCenterSystem;
|
||||
[Commands.linkSignatureToSystem]: CommandLinkSignatureToSystem;
|
||||
[Commands.signaturesUpdated]: CommandLinkSignaturesUpdated;
|
||||
}
|
||||
|
||||
export interface MapHandlers {
|
||||
@@ -114,10 +118,12 @@ export enum OutCommand {
|
||||
getCharacterJumps = 'get_character_jumps',
|
||||
getSignatures = 'get_signatures',
|
||||
getSystemStaticInfos = 'get_system_static_infos',
|
||||
getConnectionInfo = 'get_connection_info',
|
||||
updateConnectionTimeStatus = 'update_connection_time_status',
|
||||
updateConnectionMassStatus = 'update_connection_mass_status',
|
||||
updateConnectionShipSizeType = 'update_connection_ship_size_type',
|
||||
updateConnectionLocked = 'update_connection_locked',
|
||||
updateConnectionCustomInfo = 'update_connection_custom_info',
|
||||
updateSignatures = 'update_signatures',
|
||||
updateSystemName = 'update_system_name',
|
||||
updateSystemDescription = 'update_system_description',
|
||||
@@ -143,6 +149,7 @@ export enum OutCommand {
|
||||
|
||||
getUserSettings = 'get_user_settings',
|
||||
updateUserSettings = 'update_user_settings',
|
||||
unlinkSignature = 'unlink_signature',
|
||||
}
|
||||
|
||||
export type OutCommandHandler = <T = any>(event: { type: OutCommand; data: any }) => Promise<T>;
|
||||
|
||||
@@ -7,6 +7,7 @@ import { SolarSystemConnection } from '@/hooks/Mapper/types/connection.ts';
|
||||
|
||||
export type MapUnionTypes = {
|
||||
wormholesData: Record<string, WormholeDataRaw>;
|
||||
wormholes: WormholeDataRaw[];
|
||||
effects: Record<string, EffectRaw>;
|
||||
characters: CharacterTypeRaw[];
|
||||
userCharacters: string[];
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
import { SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types';
|
||||
|
||||
export type SystemSignature = {
|
||||
eve_id: string;
|
||||
kind: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
group: string;
|
||||
linked_system?: SolarSystemStaticInfoRaw;
|
||||
updated_at?: string;
|
||||
};
|
||||
|
||||
export enum SignatureGroup {
|
||||
CosmicSignature = 'Cosmic Signature',
|
||||
Wormhole = 'Wormhole',
|
||||
GasSite = 'Gas Site',
|
||||
RelicSite = 'Relic Site',
|
||||
DataSite = 'Data Site',
|
||||
OreSite = 'Ore Site',
|
||||
CombatSite = 'Combat Site',
|
||||
Wormhole = 'Wormhole',
|
||||
CosmicSignature = 'Cosmic Signature',
|
||||
}
|
||||
|
||||
export type GroupType = {
|
||||
@@ -26,3 +16,14 @@ export type GroupType = {
|
||||
w: number;
|
||||
h: number;
|
||||
};
|
||||
|
||||
export type SystemSignature = {
|
||||
eve_id: string;
|
||||
kind: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
group: SignatureGroup;
|
||||
type: string;
|
||||
linked_system?: SolarSystemStaticInfoRaw;
|
||||
updated_at?: string;
|
||||
};
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
export enum Respawn {
|
||||
static = 'static',
|
||||
wandering = 'wandering',
|
||||
reverse = 'reverse',
|
||||
}
|
||||
|
||||
export type WormholeDataRaw = {
|
||||
dest: string;
|
||||
id: number;
|
||||
@@ -5,7 +11,7 @@ export type WormholeDataRaw = {
|
||||
mass_regen: number;
|
||||
max_mass_per_jump: number;
|
||||
name: string;
|
||||
sibling_groups: any;
|
||||
respawn: Respawn[];
|
||||
src: string[];
|
||||
static: boolean;
|
||||
total_mass: number;
|
||||
|
||||
26
assets/js/hooks/Mapper/utils/abstractContextProvider.tsx
Normal file
26
assets/js/hooks/Mapper/utils/abstractContextProvider.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { createContext, ReactNode, useContext, useState } from 'react';
|
||||
|
||||
type ContextType<T> = {
|
||||
value: T;
|
||||
setValue: (newValue: T) => void;
|
||||
};
|
||||
|
||||
export const createGenericContext = <T,>() => {
|
||||
const context = createContext<ContextType<T> | undefined>(undefined);
|
||||
|
||||
const Provider = ({ children, initialValue }: { children: ReactNode; initialValue: T }) => {
|
||||
const [value, setValue] = useState<T>(initialValue);
|
||||
|
||||
return <context.Provider value={{ value, setValue }}>{children}</context.Provider>;
|
||||
};
|
||||
|
||||
const useContextValue = () => {
|
||||
const contextValue = useContext(context);
|
||||
if (!contextValue) {
|
||||
throw new Error('useContextValue must be used within a Provider');
|
||||
}
|
||||
return contextValue;
|
||||
};
|
||||
|
||||
return { Provider, useContextValue };
|
||||
};
|
||||
@@ -8,7 +8,7 @@ export default {
|
||||
const selector = '#' + this.el.id;
|
||||
|
||||
const droppable = new Droppable(containers, {
|
||||
delay: 150,
|
||||
delay: 100,
|
||||
draggable: '.draggable',
|
||||
dropzone: '.dropzone',
|
||||
mirror: {
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
"react-event-hook": "^3.1.2",
|
||||
"react-flow-renderer": "^10.3.17",
|
||||
"react-grid-layout": "^1.3.4",
|
||||
"react-hook-form": "^7.53.1",
|
||||
"react-usestateref": "^1.0.9",
|
||||
"reactflow": "^11.10.4",
|
||||
"rxjs": "^7.8.1",
|
||||
|
||||
BIN
assets/static/images/amarr-180.png
Normal file
BIN
assets/static/images/amarr-180.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 187 KiB |
BIN
assets/static/images/caldaria-180.png
Normal file
BIN
assets/static/images/caldaria-180.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 109 KiB |
BIN
assets/static/images/gallente-180.png
Normal file
BIN
assets/static/images/gallente-180.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 205 KiB |
BIN
assets/static/images/mataria-180.png
Normal file
BIN
assets/static/images/mataria-180.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 262 KiB |
@@ -3230,6 +3230,11 @@ react-grid-layout@^1.3.4:
|
||||
react-resizable "^3.0.5"
|
||||
resize-observer-polyfill "^1.5.1"
|
||||
|
||||
react-hook-form@^7.53.1:
|
||||
version "7.53.1"
|
||||
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.53.1.tgz#3f2cd1ed2b3af99416a4ac674da2d526625add67"
|
||||
integrity sha512-6aiQeBda4zjcuaugWvim9WsGqisoUk+etmFEsSUMm451/Ic8L/UAb7sRtMj3V+Hdzm6mMjU1VhiSzYUZeBm0Vg==
|
||||
|
||||
react-is@^16.13.1:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
|
||||
|
||||
@@ -55,11 +55,11 @@ map_subscriptions_enabled =
|
||||
|
||||
map_subscription_characters_limit =
|
||||
config_dir
|
||||
|> get_int_from_path_or_env("WANDERER_MAP_SUBSCRIPTION_CHARACTERS_LIMIT", 100)
|
||||
|> get_int_from_path_or_env("WANDERER_MAP_SUBSCRIPTION_CHARACTERS_LIMIT", 10_000)
|
||||
|
||||
map_subscription_hubs_limit =
|
||||
config_dir
|
||||
|> get_int_from_path_or_env("WANDERER_MAP_SUBSCRIPTION_HUBS_LIMIT", 10)
|
||||
|> get_int_from_path_or_env("WANDERER_MAP_SUBSCRIPTION_HUBS_LIMIT", 100)
|
||||
|
||||
wallet_tracking_enabled =
|
||||
config_dir
|
||||
@@ -138,6 +138,7 @@ config :ueberauth, WandererApp.Ueberauth.Strategy.Eve.OAuth,
|
||||
System.get_env("EVE_CLIENT_WITH_CORP_WALLET_SECRET", "<EVE_CLIENT_WITH_CORP_WALLET_SECRET>")
|
||||
|
||||
config :logger,
|
||||
truncate: :infinity,
|
||||
level:
|
||||
String.to_existing_atom(
|
||||
System.get_env(
|
||||
@@ -171,43 +172,65 @@ config :wanderer_app, WandererApp.Scheduler,
|
||||
timeout: :infinity
|
||||
|
||||
if config_env() == :prod do
|
||||
database_url =
|
||||
System.get_env("DATABASE_URL") ||
|
||||
raise """
|
||||
environment variable DATABASE_URL is missing.
|
||||
For example: ecto://USER:PASS@HOST/DATABASE
|
||||
"""
|
||||
database_unix_socket =
|
||||
System.get_env("DATABASE_UNIX_SOCKET")
|
||||
|
||||
maybe_ipv6 =
|
||||
config_dir
|
||||
|> get_var_from_path_or_env("ECTO_IPV6", "false")
|
||||
|> String.to_existing_atom()
|
||||
database =
|
||||
database_unix_socket
|
||||
|> case do
|
||||
true -> [:inet6]
|
||||
_ -> []
|
||||
end
|
||||
nil ->
|
||||
System.get_env("DATABASE_URL") ||
|
||||
raise """
|
||||
environment variable DATABASE_URL is missing.
|
||||
For example: ecto://USER:PASS@HOST/DATABASE
|
||||
"""
|
||||
|
||||
db_ssl_enabled =
|
||||
config_dir
|
||||
|> get_var_from_path_or_env("DATABASE_SSL_ENABLED", "false")
|
||||
|> String.to_existing_atom()
|
||||
|
||||
db_ssl_verify_none =
|
||||
config_dir
|
||||
|> get_var_from_path_or_env("DATABASE_SSL_VERIFY_NONE", "false")
|
||||
|> String.to_existing_atom()
|
||||
|
||||
client_opts =
|
||||
if db_ssl_verify_none do
|
||||
[verify: :verify_none]
|
||||
_ ->
|
||||
System.get_env("DATABASE_NAME") ||
|
||||
raise """
|
||||
environment variable DATABASE_NAME is missing.
|
||||
For example: "wanderer"
|
||||
"""
|
||||
end
|
||||
|
||||
config :wanderer_app, WandererApp.Repo,
|
||||
url: database_url,
|
||||
ssl: db_ssl_enabled,
|
||||
ssl_opts: client_opts,
|
||||
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"),
|
||||
socket_options: maybe_ipv6
|
||||
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
|
||||
|
||||
if not is_nil(database_unix_socket) do
|
||||
config :wanderer_app, WandererApp.Repo,
|
||||
socket_dir: database_unix_socket,
|
||||
database: database
|
||||
else
|
||||
db_ssl_enabled =
|
||||
config_dir
|
||||
|> get_var_from_path_or_env("DATABASE_SSL_ENABLED", "false")
|
||||
|> String.to_existing_atom()
|
||||
|
||||
db_ssl_verify_none =
|
||||
config_dir
|
||||
|> get_var_from_path_or_env("DATABASE_SSL_VERIFY_NONE", "false")
|
||||
|> String.to_existing_atom()
|
||||
|
||||
client_opts =
|
||||
if db_ssl_verify_none do
|
||||
[verify: :verify_none]
|
||||
end
|
||||
|
||||
maybe_ipv6 =
|
||||
config_dir
|
||||
|> get_var_from_path_or_env("ECTO_IPV6", "false")
|
||||
|> String.to_existing_atom()
|
||||
|> case do
|
||||
true -> [:inet6]
|
||||
_ -> []
|
||||
end
|
||||
|
||||
config :wanderer_app, WandererApp.Repo,
|
||||
url: database,
|
||||
ssl: db_ssl_enabled,
|
||||
ssl_opts: client_opts,
|
||||
socket_options: maybe_ipv6
|
||||
end
|
||||
|
||||
# The secret key base is used to sign/encrypt cookies and other secrets.
|
||||
# A default value is used in config/dev.exs and config/test.exs but you
|
||||
|
||||
20
fly.toml
20
fly.toml
@@ -3,9 +3,10 @@
|
||||
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
|
||||
#
|
||||
|
||||
app = 'wanderer'
|
||||
app = 'wanderer-test'
|
||||
primary_region = 'ams'
|
||||
kill_signal = 'SIGTERM'
|
||||
swap_size_mb = 512
|
||||
|
||||
[build]
|
||||
|
||||
@@ -13,18 +14,14 @@ kill_signal = 'SIGTERM'
|
||||
release_command = '/app/bin/migrate.sh'
|
||||
|
||||
[env]
|
||||
PHX_HOST = 'wanderer.fly.dev'
|
||||
PHX_HOST = 'wanderer-test.fly.dev'
|
||||
PHX_SERVER = 'true'
|
||||
PORT = '8080'
|
||||
|
||||
[metrics]
|
||||
port = 4021
|
||||
path = "/metrics"
|
||||
|
||||
[http_service]
|
||||
internal_port = 8080
|
||||
force_https = true
|
||||
auto_stop_machines = false
|
||||
auto_stop_machines = 'off'
|
||||
auto_start_machines = false
|
||||
min_machines_running = 0
|
||||
processes = ['app']
|
||||
@@ -35,6 +32,9 @@ path = "/metrics"
|
||||
soft_limit = 1000
|
||||
|
||||
[[vm]]
|
||||
memory = '1gb'
|
||||
cpu_kind = 'shared'
|
||||
cpus = 1
|
||||
size = 'shared-cpu-1x'
|
||||
|
||||
[[metrics]]
|
||||
port = 4021
|
||||
path = '/metrics'
|
||||
https = false
|
||||
|
||||
@@ -7,6 +7,8 @@ defmodule WandererApp do
|
||||
if it comes from the database, an external API or others.
|
||||
"""
|
||||
|
||||
require Logger
|
||||
|
||||
@doc """
|
||||
When used, dispatch to the appropriate domain service
|
||||
"""
|
||||
@@ -30,4 +32,19 @@ defmodule WandererApp do
|
||||
defmacro __using__(which) when is_atom(which) do
|
||||
apply(__MODULE__, which, [])
|
||||
end
|
||||
|
||||
def log_exception(kind, reason, stacktrace) do
|
||||
reason = Exception.normalize(kind, reason, stacktrace)
|
||||
|
||||
crash_reason =
|
||||
case kind do
|
||||
:throw -> {{:nocatch, reason}, stacktrace}
|
||||
_ -> {reason, stacktrace}
|
||||
end
|
||||
|
||||
Logger.error(
|
||||
Exception.format(kind, reason, stacktrace),
|
||||
crash_reason: crash_reason
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,8 +4,6 @@ defmodule WandererApp.Api.Calculations.CalcMapPermissions do
|
||||
use Ash.Resource.Calculation
|
||||
require Ash.Query
|
||||
|
||||
import Bitwise
|
||||
|
||||
@impl true
|
||||
def load(_query, _opts, _context) do
|
||||
[
|
||||
@@ -17,116 +15,8 @@ defmodule WandererApp.Api.Calculations.CalcMapPermissions do
|
||||
end
|
||||
|
||||
@impl true
|
||||
def calculate([record], _opts, %{actor: actor}) do
|
||||
characters = actor.characters
|
||||
|
||||
character_ids = characters |> Enum.map(& &1.id)
|
||||
character_eve_ids = characters |> Enum.map(& &1.eve_id)
|
||||
|
||||
character_corporation_ids =
|
||||
characters |> Enum.map(& &1.corporation_id) |> Enum.map(&to_string/1)
|
||||
|
||||
character_alliance_ids = characters |> Enum.map(& &1.alliance_id) |> Enum.map(&to_string/1)
|
||||
|
||||
result =
|
||||
record.acls
|
||||
|> Enum.reduce([0, 0], fn acl, acc ->
|
||||
is_owner? = acl.owner_id in character_ids
|
||||
|
||||
is_character_member? =
|
||||
acl.members |> Enum.any?(fn member -> member.eve_character_id in character_eve_ids end)
|
||||
|
||||
is_corporation_member? =
|
||||
acl.members
|
||||
|> Enum.any?(fn member -> member.eve_corporation_id in character_corporation_ids end)
|
||||
|
||||
is_alliance_member? =
|
||||
acl.members
|
||||
|> Enum.any?(fn member -> member.eve_alliance_id in character_alliance_ids end)
|
||||
|
||||
if is_owner? || is_character_member? || is_corporation_member? || is_alliance_member? do
|
||||
case acc do
|
||||
[_, -1] ->
|
||||
[-1, -1]
|
||||
|
||||
[-1, char_acc] ->
|
||||
char_acl_mask =
|
||||
acl.members
|
||||
|> Enum.filter(fn member ->
|
||||
member.eve_character_id in character_eve_ids
|
||||
end)
|
||||
|> Enum.reduce(0, fn member, acc ->
|
||||
case acc do
|
||||
-1 -> -1
|
||||
_ -> WandererApp.Permissions.calc_role_mask(member.role, acc)
|
||||
end
|
||||
end)
|
||||
|
||||
char_acc =
|
||||
case char_acl_mask do
|
||||
-1 -> -1
|
||||
_ -> char_acc ||| char_acl_mask
|
||||
end
|
||||
|
||||
[-1, char_acc]
|
||||
|
||||
[any_acc, char_acc] ->
|
||||
any_acl_mask =
|
||||
acl.members
|
||||
|> Enum.filter(fn member ->
|
||||
member.eve_character_id in character_eve_ids ||
|
||||
member.eve_corporation_id in character_corporation_ids ||
|
||||
member.eve_alliance_id in character_alliance_ids
|
||||
end)
|
||||
|> Enum.reduce(0, fn member, acc ->
|
||||
case acc do
|
||||
-1 -> -1
|
||||
_ -> WandererApp.Permissions.calc_role_mask(member.role, acc)
|
||||
end
|
||||
end)
|
||||
|
||||
char_acl_mask =
|
||||
acl.members
|
||||
|> Enum.filter(fn member ->
|
||||
member.eve_character_id in character_eve_ids
|
||||
end)
|
||||
|> Enum.reduce(0, fn member, acc ->
|
||||
case acc do
|
||||
-1 -> -1
|
||||
_ -> WandererApp.Permissions.calc_role_mask(member.role, acc)
|
||||
end
|
||||
end)
|
||||
|
||||
any_acc =
|
||||
case any_acl_mask do
|
||||
-1 -> -1
|
||||
_ -> any_acc ||| any_acl_mask
|
||||
end
|
||||
|
||||
char_acc =
|
||||
case char_acl_mask do
|
||||
-1 -> -1
|
||||
_ -> char_acc ||| char_acl_mask
|
||||
end
|
||||
|
||||
[any_acc, char_acc]
|
||||
end
|
||||
else
|
||||
acc
|
||||
end
|
||||
end)
|
||||
|
||||
case result do
|
||||
[_, -1] ->
|
||||
[-1]
|
||||
|
||||
[-1, char_acc] ->
|
||||
[char_acc]
|
||||
|
||||
[any_acc, _char_acc] ->
|
||||
[any_acc]
|
||||
end
|
||||
end
|
||||
def calculate([record], _opts, %{actor: actor}),
|
||||
do: WandererApp.Permissions.check_characters_access(actor.characters, record.acls)
|
||||
|
||||
@impl true
|
||||
def calculate(_records, _opts, _context) do
|
||||
|
||||
@@ -17,12 +17,12 @@ defmodule WandererApp.Api.MapCharacterSettings do
|
||||
action: :read_by_map
|
||||
)
|
||||
|
||||
define(:tracked_by_map,
|
||||
action: :tracked_by_map
|
||||
define(:tracked_by_map_filtered,
|
||||
action: :tracked_by_map_filtered
|
||||
)
|
||||
|
||||
define(:tracked_by_map_all,
|
||||
action: :read_tracked_by_map
|
||||
action: :tracked_by_map_all
|
||||
)
|
||||
|
||||
define(:track, action: :track)
|
||||
@@ -38,7 +38,7 @@ defmodule WandererApp.Api.MapCharacterSettings do
|
||||
|
||||
defaults [:create, :read, :update, :destroy]
|
||||
|
||||
read :tracked_by_map do
|
||||
read :tracked_by_map_filtered do
|
||||
argument(:map_id, :string, allow_nil?: false)
|
||||
argument(:character_ids, {:array, :uuid}, allow_nil?: false)
|
||||
|
||||
@@ -52,7 +52,7 @@ defmodule WandererApp.Api.MapCharacterSettings do
|
||||
filter(expr(map_id == ^arg(:map_id)))
|
||||
end
|
||||
|
||||
read :read_tracked_by_map do
|
||||
read :tracked_by_map_all do
|
||||
argument(:map_id, :string, allow_nil?: false)
|
||||
filter(expr(map_id == ^arg(:map_id) and tracked == true))
|
||||
end
|
||||
|
||||
@@ -28,6 +28,7 @@ defmodule WandererApp.Api.MapConnection do
|
||||
define(:update_time_status, action: :update_time_status)
|
||||
define(:update_ship_size_type, action: :update_ship_size_type)
|
||||
define(:update_locked, action: :update_locked)
|
||||
define(:update_custom_info, action: :update_custom_info)
|
||||
end
|
||||
|
||||
actions do
|
||||
@@ -87,6 +88,10 @@ defmodule WandererApp.Api.MapConnection do
|
||||
update :update_locked do
|
||||
accept [:locked]
|
||||
end
|
||||
|
||||
update :update_custom_info do
|
||||
accept [:custom_info]
|
||||
end
|
||||
end
|
||||
|
||||
attributes do
|
||||
@@ -131,6 +136,10 @@ defmodule WandererApp.Api.MapConnection do
|
||||
|
||||
attribute :locked, :boolean
|
||||
|
||||
attribute :custom_info, :string do
|
||||
allow_nil? true
|
||||
end
|
||||
|
||||
create_timestamp(:inserted_at)
|
||||
update_timestamp(:updated_at)
|
||||
end
|
||||
|
||||
@@ -15,6 +15,8 @@ defmodule WandererApp.Api.MapSystemSignature do
|
||||
define(:create, action: :create)
|
||||
define(:update, action: :update)
|
||||
define(:update_linked_system, action: :update_linked_system)
|
||||
define(:update_type, action: :update_type)
|
||||
define(:update_group, action: :update_group)
|
||||
|
||||
define(:by_id,
|
||||
get_by: [:id],
|
||||
@@ -32,7 +34,8 @@ defmodule WandererApp.Api.MapSystemSignature do
|
||||
:name,
|
||||
:description,
|
||||
:kind,
|
||||
:group
|
||||
:group,
|
||||
:type
|
||||
]
|
||||
|
||||
defaults [:read, :destroy]
|
||||
@@ -51,7 +54,8 @@ defmodule WandererApp.Api.MapSystemSignature do
|
||||
:name,
|
||||
:description,
|
||||
:kind,
|
||||
:group
|
||||
:group,
|
||||
:type
|
||||
]
|
||||
|
||||
argument :system_id, :uuid, allow_nil?: false
|
||||
@@ -68,7 +72,7 @@ defmodule WandererApp.Api.MapSystemSignature do
|
||||
:description,
|
||||
:kind,
|
||||
:group,
|
||||
:linked_system_id
|
||||
:type
|
||||
]
|
||||
|
||||
primary? true
|
||||
@@ -79,6 +83,14 @@ defmodule WandererApp.Api.MapSystemSignature do
|
||||
accept [:linked_system_id]
|
||||
end
|
||||
|
||||
update :update_type do
|
||||
accept [:type]
|
||||
end
|
||||
|
||||
update :update_group do
|
||||
accept [:group]
|
||||
end
|
||||
|
||||
read :by_system_id do
|
||||
argument(:system_id, :string, allow_nil?: false)
|
||||
|
||||
@@ -105,6 +117,10 @@ defmodule WandererApp.Api.MapSystemSignature do
|
||||
allow_nil? true
|
||||
end
|
||||
|
||||
attribute :type, :string do
|
||||
allow_nil? true
|
||||
end
|
||||
|
||||
attribute :linked_system_id, :integer do
|
||||
allow_nil? true
|
||||
end
|
||||
|
||||
@@ -8,6 +8,10 @@ defmodule WandererApp.Api.UserActivity do
|
||||
postgres do
|
||||
repo(WandererApp.Repo)
|
||||
table("user_activity_v1")
|
||||
|
||||
custom_indexes do
|
||||
index [:entity_id, :event_type, :inserted_at], unique: true
|
||||
end
|
||||
end
|
||||
|
||||
code_interface do
|
||||
@@ -104,6 +108,8 @@ defmodule WandererApp.Api.UserActivity do
|
||||
update_timestamp(:updated_at)
|
||||
end
|
||||
|
||||
|
||||
|
||||
relationships do
|
||||
belongs_to :character, WandererApp.Api.Character do
|
||||
allow_nil? true
|
||||
|
||||
@@ -3,6 +3,8 @@ defmodule WandererApp.Application do
|
||||
|
||||
use Application
|
||||
|
||||
require Logger
|
||||
|
||||
@impl true
|
||||
def start(_type, _args) do
|
||||
children =
|
||||
@@ -45,7 +47,16 @@ defmodule WandererApp.Application do
|
||||
# See https://hexdocs.pm/elixir/Supervisor.html
|
||||
# for other strategies and supported options
|
||||
opts = [strategy: :one_for_one, name: WandererApp.Supervisor]
|
||||
|
||||
Supervisor.start_link(children, opts)
|
||||
|> case do
|
||||
{:ok, _pid} = ok ->
|
||||
ok
|
||||
|
||||
{:error, info} = e ->
|
||||
Logger.error("Failed to start application: #{inspect(info)}")
|
||||
e
|
||||
end
|
||||
end
|
||||
|
||||
# Tell Phoenix to update the endpoint configuration
|
||||
|
||||
@@ -73,7 +73,8 @@ defmodule WandererApp.Character.Tracker do
|
||||
case WandererApp.Esi.get_character_info(eve_id) do
|
||||
{:ok, info} ->
|
||||
{:ok, character_state} = WandererApp.Character.get_character_state(character_id)
|
||||
update = maybe_update_corporation(character_state, info)
|
||||
|
||||
update = maybe_update_corporation(character_state, eve_id |> String.to_integer())
|
||||
WandererApp.Character.update_character_state(character_id, update)
|
||||
|
||||
:ok
|
||||
@@ -103,7 +104,9 @@ defmodule WandererApp.Character.Tracker do
|
||||
end
|
||||
|
||||
def update_ship(%{character_id: character_id, track_ship: true} = character_state) do
|
||||
case WandererApp.Character.get_character(character_id) do
|
||||
character_id
|
||||
|> WandererApp.Character.get_character()
|
||||
|> case do
|
||||
{:ok, %{eve_id: eve_id, access_token: access_token}} when not is_nil(access_token) ->
|
||||
WandererApp.Cache.has_key?("character:#{character_id}:ship_forbidden")
|
||||
|> case do
|
||||
@@ -541,12 +544,17 @@ defmodule WandererApp.Character.Tracker do
|
||||
|
||||
defp maybe_update_corporation(
|
||||
state,
|
||||
%{
|
||||
"corporation_id" => corporation_id
|
||||
} = _info
|
||||
character_eve_id
|
||||
)
|
||||
when not is_nil(corporation_id),
|
||||
do: update_corporation(state, corporation_id)
|
||||
when not is_nil(character_eve_id) and is_integer(character_eve_id) do
|
||||
case WandererApp.Esi.post_characters_affiliation([character_eve_id]) do
|
||||
{:ok, [character_aff_info]} when not is_nil(character_aff_info) ->
|
||||
update_corporation(state, character_aff_info |> Map.get("corporation_id"))
|
||||
|
||||
error ->
|
||||
state
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_update_corporation(
|
||||
state,
|
||||
|
||||
@@ -5,6 +5,10 @@ defmodule WandererApp.Esi do
|
||||
defdelegate get_alliance_info(eve_id, opts \\ []), to: WandererApp.Esi.ApiClient
|
||||
defdelegate get_corporation_info(eve_id, opts \\ []), to: WandererApp.Esi.ApiClient
|
||||
defdelegate get_character_info(eve_id, opts \\ []), to: WandererApp.Esi.ApiClient
|
||||
|
||||
defdelegate post_characters_affiliation(character_eve_ids, opts \\ []),
|
||||
to: WandererApp.Esi.ApiClient
|
||||
|
||||
defdelegate get_character_wallet(character_eve_id, opts \\ []), to: WandererApp.Esi.ApiClient
|
||||
defdelegate get_corporation_wallets(corporation_id, opts \\ []), to: WandererApp.Esi.ApiClient
|
||||
|
||||
|
||||
@@ -37,21 +37,32 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
|
||||
@logger Application.compile_env(:wanderer_app, :logger)
|
||||
|
||||
def get_server_status, do: _get("/status")
|
||||
def get_server_status, do: get("/status")
|
||||
|
||||
def set_autopilot_waypoint(add_to_beginning, clear_other_waypoints, destination_id, opts \\ []) do
|
||||
_post_esi(
|
||||
"/ui/autopilot/waypoint",
|
||||
opts
|
||||
|> Keyword.merge(
|
||||
params: %{
|
||||
add_to_beginning: add_to_beginning,
|
||||
clear_other_waypoints: clear_other_waypoints,
|
||||
destination_id: destination_id
|
||||
}
|
||||
def set_autopilot_waypoint(add_to_beginning, clear_other_waypoints, destination_id, opts \\ []),
|
||||
do:
|
||||
post_esi(
|
||||
"/ui/autopilot/waypoint",
|
||||
opts
|
||||
|> Keyword.merge(
|
||||
params: %{
|
||||
add_to_beginning: add_to_beginning,
|
||||
clear_other_waypoints: clear_other_waypoints,
|
||||
destination_id: destination_id
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
def post_characters_affiliation(character_eve_ids, _opts)
|
||||
when is_list(character_eve_ids),
|
||||
do:
|
||||
post(
|
||||
"#{@base_url}/characters/affiliation/",
|
||||
json: character_eve_ids,
|
||||
params: %{
|
||||
datasource: "tranquility"
|
||||
}
|
||||
)
|
||||
|
||||
def find_routes(map_id, origin, hubs, routes_settings) do
|
||||
origin = origin |> String.to_integer()
|
||||
@@ -173,7 +184,7 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
routes =
|
||||
all_routes
|
||||
|> Enum.map(fn route_info ->
|
||||
_map_route_info(route_info)
|
||||
map_route_info(route_info)
|
||||
end)
|
||||
|> Enum.filter(fn route_info -> not is_nil(route_info) end)
|
||||
|
||||
@@ -189,7 +200,7 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
{:ok, result}
|
||||
|
||||
_ ->
|
||||
case _get_all_routes_custom(hubs, origin, params) do
|
||||
case get_all_routes_custom(hubs, origin, params) do
|
||||
{:ok, result} ->
|
||||
WandererApp.Cache.insert(
|
||||
cache_key,
|
||||
@@ -199,22 +210,21 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
|
||||
{:ok, result}
|
||||
|
||||
{:error, error} ->
|
||||
@logger.error("Error getting custom routes for #{inspect(origin)}: #{inspect(error)}")
|
||||
{:error, _error} ->
|
||||
@logger.error("Error getting custom routes for #{inspect(origin)}: #{inspect(hubs)}")
|
||||
|
||||
@logger.error(
|
||||
"Error getting custom routes for #{inspect(origin)}: #{inspect(params)}"
|
||||
)
|
||||
|
||||
_get_all_routes_eve(hubs, origin, params, opts)
|
||||
get_all_routes_eve(hubs, origin, params, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp _get_all_routes_custom(hubs, origin, params),
|
||||
defp get_all_routes_custom(hubs, origin, params),
|
||||
do:
|
||||
_post(
|
||||
post(
|
||||
"#{get_custom_route_base_url()}/route/multiple",
|
||||
[
|
||||
json: %{
|
||||
@@ -228,7 +238,7 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
|> Keyword.merge(@timeout_opts)
|
||||
)
|
||||
|
||||
def _get_all_routes_eve(hubs, origin, params, opts),
|
||||
def get_all_routes_eve(hubs, origin, params, opts),
|
||||
do:
|
||||
{:ok,
|
||||
hubs
|
||||
@@ -297,7 +307,7 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
opts: [ttl: @ttl]
|
||||
)
|
||||
def get_character_info(eve_id, opts \\ []) do
|
||||
case _get(
|
||||
case get(
|
||||
"/characters/#{eve_id}/",
|
||||
opts |> _with_cache_opts()
|
||||
) do
|
||||
@@ -374,7 +384,7 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
|
||||
defp _get_routes_eve(origin, destination, params, opts),
|
||||
do:
|
||||
_get(
|
||||
get(
|
||||
"/route/#{origin}/#{destination}/?#{params |> Plug.Conn.Query.encode()}",
|
||||
opts |> _with_cache_opts()
|
||||
)
|
||||
@@ -383,14 +393,14 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
|
||||
defp _get_alliance_info(alliance_eve_id, info_path, opts),
|
||||
do:
|
||||
_get(
|
||||
get(
|
||||
"/alliances/#{alliance_eve_id}/#{info_path}",
|
||||
opts |> _with_cache_opts()
|
||||
)
|
||||
|
||||
defp _get_corporation_info(corporation_eve_id, info_path, opts),
|
||||
do:
|
||||
_get(
|
||||
get(
|
||||
"/corporations/#{corporation_eve_id}/#{info_path}",
|
||||
opts |> _with_cache_opts()
|
||||
)
|
||||
@@ -405,7 +415,7 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
character_id = opts |> Keyword.get(:character_id, nil)
|
||||
|
||||
if not _is_access_token_expired?(character_id) do
|
||||
_get(
|
||||
get(
|
||||
path,
|
||||
auth_opts,
|
||||
opts
|
||||
@@ -426,7 +436,7 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
|
||||
defp _get_corporation_auth_data(corporation_eve_id, info_path, opts),
|
||||
do:
|
||||
_get(
|
||||
get(
|
||||
"/corporations/#{corporation_eve_id}/#{info_path}",
|
||||
[params: opts[:params] || []] ++
|
||||
(opts |> _get_auth_opts() |> _with_cache_opts()),
|
||||
@@ -437,14 +447,14 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
opts |> Keyword.merge(@cache_opts) |> Keyword.merge(cache_dir: System.tmp_dir!())
|
||||
end
|
||||
|
||||
defp _post_esi(path, opts),
|
||||
defp post_esi(path, opts),
|
||||
do:
|
||||
_post(
|
||||
post(
|
||||
"#{@base_url}#{path}",
|
||||
[params: opts[:params] || []] ++ (opts |> _get_auth_opts())
|
||||
)
|
||||
|
||||
defp _get(path, api_opts \\ [], opts \\ []) do
|
||||
defp get(path, api_opts \\ [], opts \\ []) do
|
||||
try do
|
||||
case Req.get("#{@base_url}#{path}", api_opts |> Keyword.merge(@retry_opts)) do
|
||||
{:ok, %{status: 200, body: body}} ->
|
||||
@@ -476,7 +486,7 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
end
|
||||
end
|
||||
|
||||
defp _post(url, opts) do
|
||||
defp post(url, opts) do
|
||||
try do
|
||||
case Req.post("#{url}", opts) do
|
||||
{:ok, %{status: status, body: body}} when status in [200, 201] ->
|
||||
@@ -514,7 +524,7 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
{:ok, token} ->
|
||||
auth_opts = [access_token: token.access_token] |> _get_auth_opts()
|
||||
|
||||
_get(
|
||||
get(
|
||||
path,
|
||||
api_opts |> Keyword.merge(auth_opts),
|
||||
opts |> Keyword.merge(retry_count: retry_count + 1)
|
||||
@@ -589,7 +599,7 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
end
|
||||
end
|
||||
|
||||
defp _map_route_info(
|
||||
defp map_route_info(
|
||||
%{
|
||||
"origin" => origin,
|
||||
"destination" => destination,
|
||||
@@ -598,14 +608,14 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
} = _route_info
|
||||
),
|
||||
do:
|
||||
_map_route_info(%{
|
||||
map_route_info(%{
|
||||
origin: origin,
|
||||
destination: destination,
|
||||
systems: result_systems,
|
||||
success: success
|
||||
})
|
||||
|
||||
defp _map_route_info(
|
||||
defp map_route_info(
|
||||
%{origin: origin, destination: destination, systems: result_systems, success: success} =
|
||||
_route_info
|
||||
) do
|
||||
@@ -627,5 +637,5 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
}
|
||||
end
|
||||
|
||||
defp _map_route_info(_), do: nil
|
||||
defp map_route_info(_), do: nil
|
||||
end
|
||||
|
||||
@@ -79,7 +79,8 @@ defmodule WandererApp.EveDataService do
|
||||
max_mass_per_jump: row["max_mass_per_jump"],
|
||||
static: row["static"],
|
||||
mass_regen: row["mass_regen"],
|
||||
sibling_groups: row["sibling_groups"]
|
||||
sibling_groups: row["sibling_groups"],
|
||||
respawn: row["respawn"]
|
||||
}
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -8,6 +8,7 @@ defmodule WandererApp.Map do
|
||||
defstruct map_id: nil,
|
||||
name: nil,
|
||||
scope: :none,
|
||||
owner_id: nil,
|
||||
characters: [],
|
||||
systems: Map.new(),
|
||||
hubs: [],
|
||||
@@ -16,11 +17,12 @@ defmodule WandererApp.Map do
|
||||
characters_limit: nil,
|
||||
hubs_limit: nil
|
||||
|
||||
def new(%{id: map_id, name: name, scope: scope, acls: acls, hubs: hubs}) do
|
||||
def new(%{id: map_id, name: name, scope: scope, owner_id: owner_id, acls: acls, hubs: hubs}) do
|
||||
map =
|
||||
struct!(__MODULE__,
|
||||
map_id: map_id,
|
||||
scope: scope,
|
||||
owner_id: owner_id,
|
||||
name: name,
|
||||
acls: acls,
|
||||
hubs: hubs
|
||||
@@ -214,7 +216,7 @@ defmodule WandererApp.Map do
|
||||
%{visible: true} = system ->
|
||||
system
|
||||
|
||||
_ ->
|
||||
_system ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
@@ -262,7 +264,7 @@ defmodule WandererApp.Map do
|
||||
case not Map.has_key?(systems, solar_system_id) do
|
||||
true ->
|
||||
map_id
|
||||
|> update_map(%{systems: Map.put_new(systems, solar_system_id, system)})
|
||||
|> update_map(%{systems: Map.put(systems, solar_system_id, system)})
|
||||
|
||||
:ok
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ defmodule WandererApp.Map.PositionCalculator do
|
||||
def get_available_positions(level, x, y, opts),
|
||||
do: adjusted_coordinates(1 + level * 2, x, y, opts)
|
||||
|
||||
defp edge_coordinates(n, opts) when n > 1 do
|
||||
defp edge_coordinates(n, _opts) when n > 1 do
|
||||
min = -div(n, 2)
|
||||
max = div(n, 2)
|
||||
# Top edge
|
||||
|
||||
@@ -183,6 +183,12 @@ defmodule WandererApp.Map.Server do
|
||||
|> map_pid!
|
||||
|> GenServer.cast({&Impl.delete_connection/2, [connection_info]})
|
||||
|
||||
def get_connection_info(map_id, connection_info) when is_binary(map_id),
|
||||
do:
|
||||
map_id
|
||||
|> map_pid!
|
||||
|> GenServer.call({&Impl.get_connection_info/2, [connection_info]}, :timer.minutes(1))
|
||||
|
||||
def update_connection_time_status(map_id, connection_info) when is_binary(map_id),
|
||||
do:
|
||||
map_id
|
||||
@@ -207,6 +213,12 @@ defmodule WandererApp.Map.Server do
|
||||
|> map_pid!
|
||||
|> GenServer.cast({&Impl.update_connection_locked/2, [connection_info]})
|
||||
|
||||
def update_connection_custom_info(map_id, connection_info) when is_binary(map_id),
|
||||
do:
|
||||
map_id
|
||||
|> map_pid!
|
||||
|> GenServer.cast({&Impl.update_connection_custom_info/2, [connection_info]})
|
||||
|
||||
@impl true
|
||||
def handle_continue(:load_state, state),
|
||||
do: {:noreply, state |> Impl.load_state(), {:continue, :start_map}}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -87,4 +87,113 @@ defmodule WandererApp.Permissions do
|
||||
delete_map: check_permission(user_permissions, @delete_map)
|
||||
}
|
||||
end
|
||||
|
||||
def check_characters_access(characters, acls) do
|
||||
character_ids = characters |> Enum.map(& &1.id)
|
||||
character_eve_ids = characters |> Enum.map(& &1.eve_id)
|
||||
|
||||
character_corporation_ids =
|
||||
characters |> Enum.map(& &1.corporation_id) |> Enum.map(&to_string/1)
|
||||
|
||||
character_alliance_ids = characters |> Enum.map(& &1.alliance_id) |> Enum.map(&to_string/1)
|
||||
|
||||
result =
|
||||
acls
|
||||
|> Enum.reduce([0, 0], fn acl, acc ->
|
||||
is_owner? = acl.owner_id in character_ids
|
||||
|
||||
is_character_member? =
|
||||
acl.members |> Enum.any?(fn member -> member.eve_character_id in character_eve_ids end)
|
||||
|
||||
is_corporation_member? =
|
||||
acl.members
|
||||
|> Enum.any?(fn member -> member.eve_corporation_id in character_corporation_ids end)
|
||||
|
||||
is_alliance_member? =
|
||||
acl.members
|
||||
|> Enum.any?(fn member -> member.eve_alliance_id in character_alliance_ids end)
|
||||
|
||||
if is_owner? || is_character_member? || is_corporation_member? || is_alliance_member? do
|
||||
case acc do
|
||||
[_, -1] ->
|
||||
[-1, -1]
|
||||
|
||||
[-1, char_acc] ->
|
||||
char_acl_mask =
|
||||
acl.members
|
||||
|> Enum.filter(fn member ->
|
||||
member.eve_character_id in character_eve_ids
|
||||
end)
|
||||
|> Enum.reduce(0, fn member, acc ->
|
||||
case acc do
|
||||
-1 -> -1
|
||||
_ -> calc_role_mask(member.role, acc)
|
||||
end
|
||||
end)
|
||||
|
||||
char_acc =
|
||||
case char_acl_mask do
|
||||
-1 -> -1
|
||||
_ -> char_acc ||| char_acl_mask
|
||||
end
|
||||
|
||||
[-1, char_acc]
|
||||
|
||||
[any_acc, char_acc] ->
|
||||
any_acl_mask =
|
||||
acl.members
|
||||
|> Enum.filter(fn member ->
|
||||
member.eve_character_id in character_eve_ids ||
|
||||
member.eve_corporation_id in character_corporation_ids ||
|
||||
member.eve_alliance_id in character_alliance_ids
|
||||
end)
|
||||
|> Enum.reduce(0, fn member, acc ->
|
||||
case acc do
|
||||
-1 -> -1
|
||||
_ -> calc_role_mask(member.role, acc)
|
||||
end
|
||||
end)
|
||||
|
||||
char_acl_mask =
|
||||
acl.members
|
||||
|> Enum.filter(fn member ->
|
||||
member.eve_character_id in character_eve_ids
|
||||
end)
|
||||
|> Enum.reduce(0, fn member, acc ->
|
||||
case acc do
|
||||
-1 -> -1
|
||||
_ -> calc_role_mask(member.role, acc)
|
||||
end
|
||||
end)
|
||||
|
||||
any_acc =
|
||||
case any_acl_mask do
|
||||
-1 -> -1
|
||||
_ -> any_acc ||| any_acl_mask
|
||||
end
|
||||
|
||||
char_acc =
|
||||
case char_acl_mask do
|
||||
-1 -> -1
|
||||
_ -> char_acc ||| char_acl_mask
|
||||
end
|
||||
|
||||
[any_acc, char_acc]
|
||||
end
|
||||
else
|
||||
acc
|
||||
end
|
||||
end)
|
||||
|
||||
case result do
|
||||
[_, -1] ->
|
||||
[-1]
|
||||
|
||||
[-1, char_acc] ->
|
||||
[char_acc]
|
||||
|
||||
[any_acc, _char_acc] ->
|
||||
[any_acc]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,4 +3,23 @@ defmodule WandererApp.MapCharacterSettingsRepo do
|
||||
|
||||
def create(settings),
|
||||
do: WandererApp.Api.MapCharacterSettings.create(settings)
|
||||
|
||||
def get_tracked_by_map_filtered(map_id, character_ids),
|
||||
do:
|
||||
WandererApp.Api.MapCharacterSettings.tracked_by_map_filtered(%{
|
||||
map_id: map_id,
|
||||
character_ids: character_ids
|
||||
})
|
||||
|
||||
def get_all_by_map(map_id),
|
||||
do: WandererApp.Api.MapCharacterSettings.read_by_map(%{map_id: map_id})
|
||||
|
||||
def get_tracked_by_map_all(map_id),
|
||||
do: WandererApp.Api.MapCharacterSettings.tracked_by_map_all(%{map_id: map_id})
|
||||
|
||||
def track(settings), do: settings |> WandererApp.Api.MapCharacterSettings.track()
|
||||
def untrack(settings), do: settings |> WandererApp.Api.MapCharacterSettings.untrack()
|
||||
|
||||
def track!(settings), do: settings |> WandererApp.Api.MapCharacterSettings.track!()
|
||||
def untrack!(settings), do: settings |> WandererApp.Api.MapCharacterSettings.untrack!()
|
||||
end
|
||||
|
||||
@@ -29,7 +29,7 @@ defmodule WandererApp.MapConnectionRepo do
|
||||
|
||||
def create!(connection), do: connection |> WandererApp.Api.MapConnection.create!()
|
||||
|
||||
def destroy(map_id, connection) do
|
||||
def destroy(map_id, connection) when not is_nil(connection) do
|
||||
{:ok, from_connections} =
|
||||
get_by_locations(map_id, connection.solar_system_source, connection.solar_system_target)
|
||||
|
||||
@@ -49,6 +49,8 @@ defmodule WandererApp.MapConnectionRepo do
|
||||
end
|
||||
end
|
||||
|
||||
def destroy(_map_id, _connection), do: :ok
|
||||
|
||||
def destroy!(connection), do: connection |> WandererApp.Api.MapConnection.destroy!()
|
||||
|
||||
def bulk_destroy!(connections) do
|
||||
@@ -82,4 +84,9 @@ defmodule WandererApp.MapConnectionRepo do
|
||||
do:
|
||||
connection
|
||||
|> WandererApp.Api.MapConnection.update_locked(update)
|
||||
|
||||
def update_custom_info(connection, update),
|
||||
do:
|
||||
connection
|
||||
|> WandererApp.Api.MapConnection.update_custom_info(update)
|
||||
end
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
defmodule WandererApp.MapRepo do
|
||||
use WandererApp, :repository
|
||||
|
||||
@default_map_options %{"layout" => "left_to_right", "store_custom_labels" => false}
|
||||
@default_map_options %{"layout" => "left_to_right", "store_custom_labels" => "false"}
|
||||
|
||||
def get(map_id, relationships \\ []) do
|
||||
map_id
|
||||
|
||||
@@ -22,38 +22,48 @@ defmodule WandererApp.MapSystemRepo do
|
||||
def get_visible_by_map(map_id),
|
||||
do: WandererApp.Api.MapSystem.read_visible_by_map(%{map_id: map_id})
|
||||
|
||||
def remove_from_map(map_id, solar_system_id, opts) do
|
||||
def remove_from_map(map_id, solar_system_id) do
|
||||
WandererApp.Api.MapSystem.read_by_map_and_solar_system!(%{
|
||||
map_id: map_id,
|
||||
solar_system_id: solar_system_id
|
||||
})
|
||||
|> cleanup_labels(opts)
|
||||
|> WandererApp.Api.MapSystem.update_tag!(%{
|
||||
tag: nil
|
||||
})
|
||||
|> WandererApp.Api.MapSystem.update_visible(%{visible: false})
|
||||
rescue
|
||||
error ->
|
||||
{:error, error}
|
||||
end
|
||||
|
||||
def cleanup_labels(%{labels: labels} = system, opts) do
|
||||
def cleanup_labels!(%{labels: labels} = system, opts) do
|
||||
store_custom_labels? =
|
||||
Keyword.get(opts, :store_custom_labels, "false") |> String.to_existing_atom()
|
||||
Keyword.get(opts, :store_custom_labels)
|
||||
|
||||
labels = get_filtered_labels(labels, store_custom_labels?)
|
||||
|
||||
system
|
||||
|> WandererApp.Api.MapSystem.update_labels!(%{
|
||||
|> update_labels!(%{
|
||||
labels: labels
|
||||
})
|
||||
end
|
||||
|
||||
def cleanup_tags(system) do
|
||||
system
|
||||
|> WandererApp.Api.MapSystem.update_tag(%{
|
||||
tag: nil
|
||||
})
|
||||
end
|
||||
|
||||
def cleanup_tags!(system) do
|
||||
system
|
||||
|> WandererApp.Api.MapSystem.update_tag!(%{
|
||||
tag: nil
|
||||
})
|
||||
end
|
||||
|
||||
def get_filtered_labels(labels, true) when is_binary(labels) do
|
||||
labels
|
||||
|> Jason.decode!()
|
||||
|> case do
|
||||
%{"customLabel" => customLabel} = labels when is_binary(customLabel) ->
|
||||
%{"customLabel" => customLabel} when is_binary(customLabel) ->
|
||||
%{"customLabel" => customLabel, "labels" => []}
|
||||
|> Jason.encode!()
|
||||
|
||||
@@ -94,8 +104,28 @@ defmodule WandererApp.MapSystemRepo do
|
||||
system
|
||||
|> WandererApp.Api.MapSystem.update_labels(update)
|
||||
|
||||
def update_labels!(system, update),
|
||||
do:
|
||||
system
|
||||
|> WandererApp.Api.MapSystem.update_labels!(update)
|
||||
|
||||
def update_position(system, update),
|
||||
do:
|
||||
system
|
||||
|> WandererApp.Api.MapSystem.update_position(update)
|
||||
|
||||
def update_position!(system, update),
|
||||
do:
|
||||
system
|
||||
|> WandererApp.Api.MapSystem.update_position!(update)
|
||||
|
||||
def update_visible(system, update),
|
||||
do:
|
||||
system
|
||||
|> WandererApp.Api.MapSystem.update_visible(update)
|
||||
|
||||
def update_visible!(system, update),
|
||||
do:
|
||||
system
|
||||
|> WandererApp.Api.MapSystem.update_visible!(update)
|
||||
end
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
defmodule WandererApp.MapUserSettingsRepo do
|
||||
use WandererApp, :repository
|
||||
|
||||
@default_form_data %{"select_on_spash" => "false", "link_signature_on_splash" => "false"}
|
||||
@default_form_data %{
|
||||
"select_on_spash" => false,
|
||||
"link_signature_on_splash" => false,
|
||||
"delete_connection_with_sigs" => false
|
||||
}
|
||||
|
||||
def get(map_id, user_id) do
|
||||
map_id
|
||||
@@ -46,4 +50,13 @@ defmodule WandererApp.MapUserSettingsRepo do
|
||||
{:ok, data} = to_form_data(user_settings)
|
||||
data
|
||||
end
|
||||
|
||||
def get_boolean_setting(settings, key, default \\ false) do
|
||||
settings
|
||||
|> Map.get(key, default)
|
||||
|> to_boolean()
|
||||
end
|
||||
|
||||
def to_boolean(value) when is_binary(value), do: value |> String.to_existing_atom()
|
||||
def to_boolean(value) when is_boolean(value), do: value
|
||||
end
|
||||
|
||||
38
lib/wanderer_app_web/components/map/map_loader.ex
Normal file
38
lib/wanderer_app_web/components/map/map_loader.ex
Normal file
@@ -0,0 +1,38 @@
|
||||
defmodule WandererAppWeb.MapLoader do
|
||||
use WandererAppWeb, :live_component
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div
|
||||
id="map-loader"
|
||||
data-loading={show_loader("map-loader")}
|
||||
data-loaded={hide_loader("map-loader")}
|
||||
class="!z-100 w-screen h-screen hidden relative"
|
||||
>
|
||||
<div class="hs-overlay-backdrop transition duration absolute inset-0 blur" />
|
||||
<div class="flex !z-[150] w-full h-full items-center justify-center">
|
||||
<div class="Loader" data-text="Wanderer">
|
||||
<span class="Loader__Circle"></span>
|
||||
<span class="Loader__Circle"></span>
|
||||
<span class="Loader__Circle"></span>
|
||||
<span class="Loader__Circle"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
defp show_loader(js \\ %JS{}, id),
|
||||
do:
|
||||
JS.show(js,
|
||||
to: "##{id}",
|
||||
transition: {"transition-opacity ease-out duration-500", "opacity-0", "opacity-100"}
|
||||
)
|
||||
|
||||
defp hide_loader(js \\ %JS{}, id),
|
||||
do:
|
||||
JS.hide(js,
|
||||
to: "##{id}",
|
||||
transition: {"transition-opacity ease-in duration-500", "opacity-100", "opacity-0"}
|
||||
)
|
||||
end
|
||||
78
lib/wanderer_app_web/components/map/map_picker.ex
Normal file
78
lib/wanderer_app_web/components/map/map_picker.ex
Normal file
@@ -0,0 +1,78 @@
|
||||
defmodule WandererAppWeb.MapPicker do
|
||||
use WandererAppWeb, :live_component
|
||||
|
||||
use LiveViewEvents
|
||||
|
||||
@impl true
|
||||
def mount(socket) do
|
||||
socket =
|
||||
socket
|
||||
|> assign(form: to_form(%{"map_slug" => nil}))
|
||||
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def update(
|
||||
%{
|
||||
current_user: current_user,
|
||||
map_slug: map_slug
|
||||
} = assigns,
|
||||
socket
|
||||
) do
|
||||
socket = handle_info_or_assign(socket, assigns)
|
||||
|
||||
{:ok,
|
||||
socket
|
||||
|> assign(form: to_form(%{"map_slug" => map_slug}))
|
||||
|> assign_async(:maps, fn ->
|
||||
get_available_maps(current_user)
|
||||
end)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div id={@id}>
|
||||
<.form
|
||||
:let={f}
|
||||
:if={not is_nil(assigns |> Map.get(:maps))}
|
||||
for={@form}
|
||||
phx-change="select"
|
||||
phx-target={@myself}
|
||||
>
|
||||
<.async_result :let={maps} assign={@maps}>
|
||||
<:loading><span class="loading loading-dots loading-xs" /></:loading>
|
||||
<:failed :let={reason}><%= reason %></:failed>
|
||||
<.input
|
||||
:if={maps}
|
||||
type="select"
|
||||
field={f[:map_slug]}
|
||||
class="select h-8 min-h-[0px] !pt-1 !pb-1 text-sm bg-neutral-900"
|
||||
placeholder="Select a map..."
|
||||
options={Enum.map(@maps.result, fn map -> {map.label, map.value} end)}
|
||||
/>
|
||||
</.async_result>
|
||||
</.form>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("select", %{"map_slug" => map_slug} = _params, socket) do
|
||||
notify_to(socket.assigns.notify_to, socket.assigns.event_name, map_slug)
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
defp get_available_maps(current_user) do
|
||||
{:ok, maps} =
|
||||
current_user
|
||||
|> WandererApp.Maps.get_available_maps()
|
||||
|
||||
{:ok, %{maps: maps |> Enum.sort_by(& &1.name, :asc) |> Enum.map(&map_map/1)}}
|
||||
end
|
||||
|
||||
defp map_map(%{name: name, slug: slug} = _map),
|
||||
do: %{label: name, value: slug}
|
||||
end
|
||||
@@ -1,62 +1,92 @@
|
||||
defmodule WandererAppWeb.UserActivity do
|
||||
use WandererAppWeb, :live_component
|
||||
use LiveViewEvents
|
||||
|
||||
attr(:stream, :any, required: true)
|
||||
attr(:page, :integer, required: true)
|
||||
attr(:end_of_stream?, :boolean, required: true)
|
||||
@impl true
|
||||
def mount(socket) do
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
def list(assigns) do
|
||||
@impl true
|
||||
def update(assigns,
|
||||
socket
|
||||
) do
|
||||
{:ok,
|
||||
socket
|
||||
|> handle_info_or_assign(assigns)}
|
||||
end
|
||||
|
||||
# attr(:can_undo_types, :list, required: false)
|
||||
# attr(:stream, :any, required: true)
|
||||
# attr(:page, :integer, required: true)
|
||||
# attr(:end_of_stream?, :boolean, required: true)
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<span
|
||||
:if={@page > 1}
|
||||
class="text-1xl fixed bottom-10 right-10 bg-zinc-700 text-white rounded-lg p-1 text-center min-w-[65px] z-50 opacity-70"
|
||||
>
|
||||
<%= @page %>
|
||||
</span>
|
||||
<ul
|
||||
id="events"
|
||||
class="space-y-4"
|
||||
phx-update="stream"
|
||||
phx-viewport-top={@page > 1 && "prev-page"}
|
||||
phx-viewport-bottom={!@end_of_stream? && "next-page"}
|
||||
phx-page-loading
|
||||
class={[
|
||||
if(@end_of_stream?, do: "pb-10", else: "pb-[calc(200vh)]"),
|
||||
if(@page == 1, do: "pt-10", else: "pt-[calc(200vh)]")
|
||||
]}
|
||||
>
|
||||
<li :for={{dom_id, activity} <- @stream} id={dom_id}>
|
||||
<.activity_entry activity={activity} />
|
||||
</li>
|
||||
</ul>
|
||||
<div :if={@end_of_stream?} class="mt-5 text-center">
|
||||
No more activity
|
||||
<div id={@id}>
|
||||
<span
|
||||
:if={@page > 1}
|
||||
class="text-1xl fixed bottom-10 right-10 bg-zinc-700 text-white rounded-lg p-1 text-center min-w-[65px] z-50 opacity-70"
|
||||
>
|
||||
<%= @page %>
|
||||
</span>
|
||||
<ul
|
||||
id="events"
|
||||
class="space-y-4"
|
||||
phx-update="stream"
|
||||
phx-viewport-top={@page > 1 && "prev-page"}
|
||||
phx-viewport-bottom={!@end_of_stream? && "next-page"}
|
||||
phx-page-loading
|
||||
class={[
|
||||
if(@end_of_stream?, do: "pb-10", else: "pb-[calc(200vh)]"),
|
||||
if(@page == 1, do: "pt-10", else: "pt-[calc(200vh)]")
|
||||
]}
|
||||
>
|
||||
<li :for={{dom_id, activity} <- @stream} id={dom_id}>
|
||||
<.activity_entry activity={activity} can_undo_types={@can_undo_types} />
|
||||
</li>
|
||||
</ul>
|
||||
<div :if={@end_of_stream?} class="mt-5 text-center">
|
||||
No more activity
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
attr(:activity, WandererApp.Api.UserActivity, required: true)
|
||||
attr(:can_undo_types, :list, required: false)
|
||||
|
||||
defp activity_entry(%{} = assigns) do
|
||||
~H"""
|
||||
<div class="flex w-full items-center justify-between space-x-2">
|
||||
<div class="flex items-center space-x-3 text-xs">
|
||||
<div class="flex items-center w-full space-x-2 p-1 hover:bg-gray-900">
|
||||
<div class="flex items-center text-xs w-[270px]">
|
||||
<p class="flex items-center space-x-1">
|
||||
<span class="w-[150px] line-clamp-1 block text-sm font-normal leading-none text-gray-400 dark:text-gray-500">
|
||||
<.local_time id={@activity.id} at={@activity.inserted_at} />
|
||||
</span>
|
||||
</p>
|
||||
<p
|
||||
:if={not is_nil(@activity.character)}
|
||||
class="flex shrink-0 items-center space-x-1 min-w-[200px]"
|
||||
>
|
||||
<.character_item character={@activity.character} />
|
||||
</p>
|
||||
</div>
|
||||
<p class="text-sm leading-[150%] text-[var(--color-gray-4)]">
|
||||
|
||||
<.character_item :if={not is_nil(@activity.character)} character={@activity.character} />
|
||||
<p :if={is_nil(@activity.character)} class="text-sm text-[var(--color-gray-4)] w-[150px]">
|
||||
System user / Administrator
|
||||
</p>
|
||||
|
||||
<p class="text-sm text-[var(--color-gray-4)] w-[15%]">
|
||||
<%= _get_event_name(@activity.event_type) %>
|
||||
</p>
|
||||
<.activity_event event_type={@activity.event_type} event_data={@activity.event_data} />
|
||||
|
||||
<div :if={@activity.event_type in @can_undo_types}>
|
||||
<button
|
||||
phx-click="undo"
|
||||
phx-value-event-data={@activity.event_data}
|
||||
phx-value-event-type={@activity.event_type}
|
||||
class="btn btn-sm btn-icon"
|
||||
>
|
||||
<.icon name="hero-arrow-uturn-left-solid" class="h-5 w-5" /> Undo
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
@@ -65,7 +95,7 @@ defmodule WandererAppWeb.UserActivity do
|
||||
|
||||
def character_item(assigns) do
|
||||
~H"""
|
||||
<div class="flex items-center gap-3 text-sm">
|
||||
<div class="flex items-center gap-3 text-sm w-[150px]">
|
||||
<div class="avatar">
|
||||
<div class="rounded-md w-8 h-8">
|
||||
<img src={member_icon_url(@character.eve_id)} alt={@character.name} />
|
||||
@@ -86,11 +116,20 @@ defmodule WandererAppWeb.UserActivity do
|
||||
<h6 class="text-base leading-[150%] font-semibold dark:text-white">
|
||||
<%= _get_event_data(@event_type, Jason.decode!(@event_data) |> Map.drop(["character_id"])) %>
|
||||
</h6>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("undo", %{"event-data" => event_data} = _params, socket) do
|
||||
# notify_to(socket.assigns.notify_to, socket.assigns.event_name, map_slug)
|
||||
IO.inspect(event_data)
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
defp _get_event_name(:hub_added), do: "Hub Added"
|
||||
defp _get_event_name(:hub_removed), do: "Hub Removed"
|
||||
defp _get_event_name(:map_connection_added), do: "Connection Added"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user