Compare commits

..

3 Commits

Author SHA1 Message Date
Dmitry Popov
b1f1098df6 chore: release version v1.13.7 2024-10-31 22:56:59 +01:00
Dmitry Popov
ed5d824c0a refactor(Map): Map event handling refactoring 2024-10-31 19:19:23 +01:00
Dmitry Popov
d8e4631981 refactor(Map): Map event handling refactoring 2024-10-31 19:19:07 +01:00
289 changed files with 7121 additions and 20122 deletions

View File

@@ -1,7 +1,12 @@
FROM elixir:1.17-otp-27
FROM elixir:1.16-otp-25
RUN apt install -yq curl gnupg
RUN apt update -yq
RUN apt install -yq curl gnupg mc inotify-tools
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

View File

@@ -2,7 +2,7 @@ version: "0.1"
services:
db:
image: postgres:13-alpine
image: postgres:14.3
restart: always
environment:
POSTGRES_USER: postgres
@@ -10,13 +10,13 @@ services:
ports:
- "5432:5432"
volumes:
- db-new:/var/lib/postgresql/data
- db:/var/lib/postgresql/data
wanderer:
environment:
PORT: 8000
DB_HOST: db
WEB_APP_URL: "http://localhost:8000"
WEB_APP_URL: "http://localhost:4444"
ERL_AFLAGS: "-kernel shell_history enabled"
build:
context: .
@@ -33,4 +33,4 @@ services:
volumes:
elixir-artifacts: {}
db-new: {}
db: {}

View File

@@ -6,4 +6,3 @@ export EVE_CLIENT_WITH_WALLET_ID="<EVE_CLIENT_WITH_WALLET_ID>"
export EVE_CLIENT_WITH_WALLET_SECRET="<EVE_CLIENT_WITH_WALLET_SECRET>"
export GIT_SHA="1111"
export WANDERER_INVITES="false"
export WANDERER_PUBLIC_API_DISABLED="false"

View File

@@ -78,23 +78,22 @@ jobs:
fetch-depth: 0
- name: 😅 Cache deps
id: cache-deps
uses: actions/cache@v4
uses: actions/cache@v3
env:
cache-name: cache-elixir-deps
with:
path: |
deps
key: ${{ runner.os }}-mix-${{ matrix.elixir }}-${{ matrix.otp }}-${{ hashFiles('**/mix.lock') }}
path: deps
key: ${{ runner.os }}-mix-${{ env.cache-name }}-${{ hashFiles('**/mix.lock') }}
restore-keys: |
${{ runner.os }}-mix-${{ matrix.elixir }}-${{ matrix.otp }}-
${{ runner.os }}-mix-${{ env.cache-name }}-
- name: 😅 Cache compiled build
id: cache-build
uses: actions/cache@v4
uses: actions/cache@v3
env:
cache-name: cache-compiled-build
with:
path: |
_build
**/_build
key: ${{ runner.os }}-build-${{ hashFiles('**/mix.lock') }}-${{ hashFiles( '**/lib/**/*.{ex,eex}', '**/config/*.exs', '**/mix.exs' ) }}
restore-keys: |
${{ runner.os }}-build-${{ hashFiles('**/mix.lock') }}-
@@ -123,9 +122,6 @@ jobs:
name: 🛠 Build Docker Images
needs: build
runs-on: ubuntu-22.04
outputs:
release-tag: ${{ steps.get-latest-tag.outputs.tag }}
release-notes: ${{ steps.get-content.outputs.string }}
permissions:
checks: write
contents: write
@@ -139,7 +135,6 @@ jobs:
matrix:
platform:
- linux/amd64
- linux/arm64
steps:
- name: Prepare
run: |
@@ -188,28 +183,15 @@ jobs:
push: true
context: .
file: ./Dockerfile
cache-from: type=gha
cache-to: type=gha,mode=max
tags: ${{ env.REGISTRY_IMAGE }}:latest,${{ env.REGISTRY_IMAGE }}:${{ steps.get-latest-tag.outputs.tag }}
labels: ${{ steps.meta.outputs.labels }}
platforms: ${{ matrix.platform }}
outputs: type=image,"name=${{ env.REGISTRY_IMAGE }}",push-by-digest=true,name-canonical=true,push=true
build-args: |
MIX_ENV=prod
BUILD_METADATA=${{ steps.meta.outputs.json }}
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
- name: Image digest
run: echo ${{ steps.build.outputs.digest }}
- uses: markpatterson27/markdown-to-output@v1
id: extract-changelog
@@ -229,54 +211,16 @@ jobs:
maxLength: 500
truncationSymbol: "…"
merge:
runs-on: ubuntu-latest
needs:
- docker
steps:
- name: Download digests
uses: actions/download-artifact@v4
- name: Discord Webhook Action
uses: tsickert/discord-webhook@v5.3.0
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.WANDERER_DOCKER_USER }}
password: ${{ secrets.WANDERER_DOCKER_PASSWORD }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ env.REGISTRY_IMAGE }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{version}},value=${{ needs.docker.outputs.release-tag }}
- name: Create manifest list and push
working-directory: /tmp/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
- name: Inspect image
run: |
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }}
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
content: ${{ steps.get-content.outputs.string }}
create-release:
name: 🏷 Create Release
runs-on: ubuntu-22.04
needs: [docker, merge]
needs: docker
if: ${{ github.ref == 'refs/heads/main' && github.event_name == 'push' }}
steps:
- name: ⬇️ Checkout repo
@@ -284,11 +228,17 @@ jobs:
with:
fetch-depth: 0
- name: Get Release Tag
id: get-latest-tag
uses: "WyriHaximus/github-action-get-previous-tag@v1"
with:
fallback: 1.0.0
- name: 🏷 Create Draft Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ needs.docker.outputs.release-tag }}
name: Release ${{ needs.docker.outputs.release-tag }}
tag_name: ${{ steps.get-latest-tag.outputs.tag }}
name: Release ${{ steps.get-latest-tag.outputs.tag }}
body: |
## Info
Commit ${{ github.sha }} was deployed to `staging`. [See code diff](${{ github.event.compare }}).
@@ -298,9 +248,3 @@ jobs:
## How to Promote?
In order to promote this to prod, edit the draft and press **"Publish release"**.
draft: true
- name: Discord Webhook Action
uses: tsickert/discord-webhook@v5.3.0
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
content: ${{ needs.docker.outputs.release-notes }}

View File

@@ -18,4 +18,4 @@ jobs:
key: ${{ secrets.SSH_KEY }}
port: ${{ secrets.PORT }}
script: |
/home/wanderer/app/deploy.sh ${{ github.event.release.tag_name }}
/app/release/linux/deploy.sh ${{ github.event.release.tag_name }}

View File

@@ -1,3 +1,3 @@
erlang 26.2.5.5
elixir 1.17.3-otp-26
erlang 25.3
elixir 1.16-otp-25
nodejs 18.0.0

File diff suppressed because it is too large Load Diff

View File

@@ -9,9 +9,6 @@ WORKDIR /app
# set build ENV
ENV MIX_ENV="prod"
# Set ERL_FLAGS for ARM compatibility
ENV ERL_FLAGS="+JPperf true"
# install mix dependencies
COPY mix.exs mix.lock ./
RUN rm -Rf _build deps && mix deps.get --only $MIX_ENV

View File

@@ -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 dont 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 dont 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,13 +54,7 @@ Now you can visit [`localhost:8000`](http://localhost:8000) from your browser.
#### Using .devcontainer
- Run devcontainer
- 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
- See how to start server in #setup section
#### Using nix flakes

View File

@@ -3,8 +3,6 @@
@import 'primereact/resources/themes/arya-blue/theme.css' layer(primereact);
/*@import 'primereact/resources/themes/bootstrap4-dark-blue/theme.css' layer(primereact);*/
@import '../js/hooks/Mapper/components/map/styles/index.scss';
@layer tailwind-base {
@tailwind base;
}
@@ -468,467 +466,3 @@ body {
transform: rotate(-360deg);
}
}
/* Map refresh */
.socket {
scale: 0.5;
width: 150px;
height: 150px;
left: 50%;
/* margin-left: -75px; */
top: 50%;
/* margin-top: -50px; */
}
.hex-brick {
background: #000;
width: 30px;
height: 17px;
position: absolute;
top: 5px;
animation-name: fade;
animation-duration: 2s;
animation-iteration-count: infinite;
-webkit-animation-name: fade;
-webkit-animation-duration: 2s;
-webkit-animation-iteration-count: infinite;
}
.hex-brick--active {
animation-name: fade-active;
-webkit-animation-name: fade-active;
}
.h2 {
transform: rotate(60deg);
-webkit-transform: rotate(60deg);
}
.h3 {
transform: rotate(-60deg);
-webkit-transform: rotate(-60deg);
}
.gel {
height: 30px;
width: 30px;
transition: all 0.3s;
-webkit-transition: all 0.3s;
position: absolute;
top: 50%;
left: 50%;
}
.center-gel {
margin-left: -15px;
margin-top: -15px;
animation-name: pulse-version;
animation-duration: 2s;
animation-iteration-count: infinite;
-webkit-animation-name: pulse-version;
-webkit-animation-duration: 2s;
-webkit-animation-iteration-count: infinite;
}
.c1 {
margin-left: -47px;
margin-top: -15px;
}
.c2 {
margin-left: -31px;
margin-top: -43px;
}
.c3 {
margin-left: 1px;
margin-top: -43px;
}
.c4 {
margin-left: 17px;
margin-top: -15px;
}
.c5 {
margin-left: -31px;
margin-top: 13px;
}
.c6 {
margin-left: 1px;
margin-top: 13px;
}
.c7 {
margin-left: -63px;
margin-top: -43px;
}
.c8 {
margin-left: 33px;
margin-top: -43px;
}
.c9 {
margin-left: -15px;
margin-top: 41px;
}
.c10 {
margin-left: -63px;
margin-top: 13px;
}
.c11 {
margin-left: 33px;
margin-top: 13px;
}
.c12 {
margin-left: -15px;
margin-top: -71px;
}
.c13 {
margin-left: -47px;
margin-top: -71px;
}
.c14 {
margin-left: 17px;
margin-top: -71px;
}
.c15 {
margin-left: -47px;
margin-top: 41px;
}
.c16 {
margin-left: 17px;
margin-top: 41px;
}
.c17 {
margin-left: -79px;
margin-top: -15px;
}
.c18 {
margin-left: 49px;
margin-top: -15px;
}
.c19 {
margin-left: -63px;
margin-top: -99px;
}
.c20 {
margin-left: 33px;
margin-top: -99px;
}
.c21 {
margin-left: 1px;
margin-top: -99px;
}
.c22 {
margin-left: -31px;
margin-top: -99px;
}
.c23 {
margin-left: -63px;
margin-top: 69px;
}
.c24 {
margin-left: 33px;
margin-top: 69px;
}
.c25 {
margin-left: 1px;
margin-top: 69px;
}
.c26 {
margin-left: -31px;
margin-top: 69px;
}
.c27 {
margin-left: -79px;
margin-top: -15px;
}
.c28 {
margin-left: -95px;
margin-top: -43px;
}
.c29 {
margin-left: -95px;
margin-top: 13px;
}
.c30 {
margin-left: 49px;
margin-top: 41px;
}
.c31 {
margin-left: -79px;
margin-top: -71px;
}
.c32 {
margin-left: -111px;
margin-top: -15px;
}
.c33 {
margin-left: 65px;
margin-top: -43px;
}
.c34 {
margin-left: 65px;
margin-top: 13px;
}
.c35 {
margin-left: -79px;
margin-top: 41px;
}
.c36 {
margin-left: 49px;
margin-top: -71px;
}
.c37 {
margin-left: 81px;
margin-top: -15px;
}
.r1 {
animation-name: pulse-version;
animation-duration: 2s;
animation-iteration-count: infinite;
animation-delay: 0.2s;
-webkit-animation-name: pulse-version;
-webkit-animation-duration: 2s;
-webkit-animation-iteration-count: infinite;
-webkit-animation-delay: 0.2s;
}
.r2 {
animation-name: pulse-version;
animation-duration: 2s;
animation-iteration-count: infinite;
animation-delay: 0.4s;
-webkit-animation-name: pulse-version;
-webkit-animation-duration: 2s;
-webkit-animation-iteration-count: infinite;
-webkit-animation-delay: 0.4s;
}
.r3 {
animation-name: pulse-version;
animation-duration: 2s;
animation-iteration-count: infinite;
animation-delay: 0.6s;
-webkit-animation-name: pulse-version;
-webkit-animation-duration: 2s;
-webkit-animation-iteration-count: infinite;
-webkit-animation-delay: 0.6s;
}
.r1 > .hex-brick {
animation-name: fade;
animation-duration: 2s;
animation-iteration-count: infinite;
animation-delay: 0.2s;
-webkit-animation-name: fade;
-webkit-animation-duration: 2s;
-webkit-animation-iteration-count: infinite;
-webkit-animation-delay: 0.2s;
}
.r1 > .hex-brick--active {
animation-name: fade-active;
-webkit-animation-name: fade-active;
}
.r2 > .hex-brick {
animation-name: fade;
animation-duration: 2s;
animation-iteration-count: infinite;
animation-delay: 0.4s;
-webkit-animation-name: fade;
-webkit-animation-duration: 2s;
-webkit-animation-iteration-count: infinite;
-webkit-animation-delay: 0.4s;
}
.r2 > .hex-brick--active {
animation-name: fade-active;
-webkit-animation-name: fade-active;
}
.r3 > .hex-brick {
animation-name: fade;
animation-duration: 2s;
animation-iteration-count: infinite;
animation-delay: 0.6s;
-webkit-animation-name: fade;
-webkit-animation-duration: 2s;
-webkit-animation-iteration-count: infinite;
-webkit-animation-delay: 0.6s;
}
.r3 > .hex-brick--active {
animation-name: fade-active;
-webkit-animation-name: fade-active;
}
@keyframes pulse-version {
0% {
-webkit-transform: scale(1);
transform: scale(1);
}
50% {
-webkit-transform: scale(0.01);
transform: scale(0.01);
}
100% {
-webkit-transform: scale(1);
transform: scale(1);
}
}
@keyframes fade {
0% {
background: #09d0e2;
}
50% {
background: #8ae6ee;
}
100% {
background: #09d0e2;
}
}
@keyframes fade-active {
0% {
background: #ff52d9;
}
50% {
background: #ff52d9;
}
100% {
background: #ff52d9;
}
}
@-webkit-keyframes pulse {
0% {
-webkit-transform: scale(1);
transform: scale(1);
}
50% {
-webkit-transform: scale(0.01);
transform: scale(0.01);
}
100% {
-webkit-transform: scale(1);
transform: scale(1);
}
}
@-webkit-keyframes fade {
0% {
background: #abf8ff;
}
50% {
background: #389ca6;
}
100% {
background: #abf8ff;
}
}
/* Map refresh END */
.inputContainer {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
}
.inputContainer > span:nth-child(1),
.inputContainer > label:nth-child(1) {
color: var(--gray-200);
font-size: 13px;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.inputContainer > :nth-child(2) {
border-bottom: 2px dotted #3f3f3f;
height: 1px;
margin: 0 12px;
}
.smallInputSwitch {
height: 100%;
display: flex;
align-items: center;
}
.smallInputSwitch .p-inputswitch {
height: 1rem;
width: 2rem;
}
.smallInputSwitch .p-inputswitch.p-inputswitch-checked .p-inputswitch-slider::before {
transform: translateX(1rem);
}
.smallInputSwitch .p-inputswitch.p-highlight .p-inputswitch-slider:before {
transform: translateX(1rem);
}
.smallInputSwitch .p-inputswitch .p-inputswitch-slider::before {
width: 0.8rem;
height: 0.8rem;
margin-top: -0.4rem;
margin-left: -3px;
}
.checkboxRoot.sizeXS {
width: 14px;
height: 14px;
}
.checkboxRoot.sizeXS .p-checkbox-box,
.checkboxRoot.sizeXS .p-checkbox-input {
width: 14px;
height: 14px;
}
.checkboxRoot.sizeM {
width: 16px;
height: 16px;
}
.checkboxRoot.sizeM .p-checkbox-box,
.checkboxRoot.sizeM .p-checkbox-input {
width: 16px;
height: 16px;
}

View File

@@ -15,10 +15,11 @@ const ErrorFallback = () => {
};
export default function MapRoot({ hooks }) {
const mapRef = useRef<MapHandlers>(null);
const providerRef = useRef<MapHandlers>(null);
const hooksRef = useRef<any>(hooks);
const mapperHandlerRefs = useRef([providerRef]);
const mapperHandlerRefs = useRef([mapRef, providerRef]);
const { handleCommand, handleMapEvent, handleMapEvents } = useMapperHandlers(mapperHandlerRefs.current, hooksRef);
@@ -40,7 +41,7 @@ export default function MapRoot({ hooks }) {
return (
<PrimeReactProvider>
<MapRootProvider fwdRef={providerRef} outCommand={handleCommand}>
<MapRootProvider fwdRef={providerRef} outCommand={handleCommand} mapRef={mapRef}>
<ErrorBoundary FallbackComponent={ErrorFallback} onError={logError}>
<ReactFlowProvider>
<MapRootContent />

View File

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

View File

@@ -1,19 +1,20 @@
import { useCallback } from 'react';
import clsx from 'clsx';
import { useAutoAnimate } from '@formkit/auto-animate/react';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts';
import { CharacterTypeRaw } from '@/hooks/Mapper/types';
import { emitMapEvent } from '@/hooks/Mapper/events';
const Characters = ({ data }: { data: CharacterTypeRaw[] }) => {
const [parent] = useAutoAnimate();
const { mapRef } = useMapRootState();
const handleSelect = useCallback((character: CharacterTypeRaw) => {
emitMapEvent({
name: Commands.centerSystem,
data: character?.location?.solar_system_id?.toString(),
});
}, []);
const handleSelect = useCallback(
(character: CharacterTypeRaw) => {
mapRef.current?.command(Commands.centerSystem, character?.location?.solar_system_id?.toString());
},
[mapRef],
);
const items = data.map(character => (
<li

View File

@@ -88,23 +88,6 @@ export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseC
setSystem(undefined);
}, []);
const onSystemTemporaryName = useCallback((temporaryName?: string) => {
const { system, outCommand } = ref.current;
if (!system) {
return;
}
outCommand({
type: OutCommand.updateSystemTemporaryName,
data: {
system_id: system,
value: temporaryName ?? '',
},
});
setSystem(undefined);
}, []);
const onSystemStatus = useCallback((status: number) => {
const { system, outCommand } = ref.current;
if (!system) {
@@ -178,7 +161,6 @@ export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseC
onLockToggle,
onHubToggle,
onSystemTag,
onSystemTemporaryName,
onSystemStatus,
onSystemLabels,
onOpenSettings,

View File

@@ -6,9 +6,6 @@ import { PrimeIcons } from 'primereact/api';
import { ContextMenuSystemProps } from '@/hooks/Mapper/components/contexts';
import { useWaypointMenu } from '@/hooks/Mapper/components/contexts/hooks';
import { FastSystemActions } from '@/hooks/Mapper/components/contexts/components';
import { useMapCheckPermissions } from '@/hooks/Mapper/mapRootProvider/hooks/api';
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace.ts';
export const useContextMenuSystemItems = ({
onDeleteSystem,
@@ -28,7 +25,6 @@ export const useContextMenuSystemItems = ({
const getStatus = useStatusMenu(systems, systemId, onSystemStatus);
const getLabels = useLabelsMenu(systems, systemId, onSystemLabels, onCustomLabelDialog);
const getWaypointMenu = useWaypointMenu(onWaypointSet);
const canLockSystem = useMapCheckPermissions([UserPermission.LOCK_SYSTEM]);
return useMemo(() => {
const system = systemId ? getSystemById(systems, systemId) : undefined;
@@ -45,8 +41,6 @@ export const useContextMenuSystemItems = ({
<FastSystemActions
systemId={systemId}
systemName={system.system_static_info.solar_system_name}
regionName={system.system_static_info.region_name}
isWH={isWormholeSpace(system.system_static_info.system_class)}
showEdit
onOpenSettings={onOpenSettings}
/>
@@ -64,25 +58,19 @@ export const useContextMenuSystemItems = ({
command: onHubToggle,
},
...(system.locked
? canLockSystem
? [
{
label: 'Unlock',
icon: PrimeIcons.LOCK_OPEN,
command: onLockToggle,
},
]
: []
? [
{
label: 'Unlock',
icon: PrimeIcons.LOCK_OPEN,
command: onLockToggle,
},
]
: [
...(canLockSystem
? [
{
label: 'Lock',
icon: PrimeIcons.LOCK,
command: onLockToggle,
},
]
: []),
{
label: 'Lock',
icon: PrimeIcons.LOCK,
command: onLockToggle,
},
{ separator: true },
{
label: 'Delete',
@@ -92,7 +80,6 @@ export const useContextMenuSystemItems = ({
]),
];
}, [
canLockSystem,
systems,
systemId,
getTags,

View File

@@ -10,7 +10,6 @@ import { WaypointSetContextHandler } from '@/hooks/Mapper/components/contexts/ty
import { FastSystemActions } from '@/hooks/Mapper/components/contexts/components';
import { useJumpPlannerMenu } from '@/hooks/Mapper/components/contexts/hooks';
import { Route } from '@/hooks/Mapper/types/routes.ts';
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace.ts';
export interface ContextMenuSystemInfoProps {
systemStatics: Map<number, SolarSystemStaticInfoRaw>;
@@ -49,6 +48,7 @@ export const ContextMenuSystemInfo: React.FC<ContextMenuSystemInfoProps> = ({
if (!systemId || !system) {
return [];
}
return [
{
className: classes.FastActions,
@@ -57,8 +57,6 @@ export const ContextMenuSystemInfo: React.FC<ContextMenuSystemInfoProps> = ({
<FastSystemActions
systemId={systemId}
systemName={system.solar_system_name}
regionName={system.region_name}
isWH={isWormholeSpace(system.system_class)}
onOpenSettings={onOpenSettings}
/>
);

View File

@@ -1,25 +1,25 @@
import * as React from 'react';
import { useCallback, useRef, useState } from 'react';
import { RefObject, useCallback, useRef, useState } from 'react';
import { ContextMenu } from 'primereact/contextmenu';
import { Commands, OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts';
import { Commands, MapHandlers, OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts';
import { WaypointSetContextHandler } from '@/hooks/Mapper/components/contexts/types.ts';
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
import * as React from 'react';
import { SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types';
import { emitMapEvent } from '@/hooks/Mapper/events';
interface UseContextMenuSystemHandlersProps {
hubs: string[];
outCommand: OutCommandHandler;
mapRef: RefObject<MapHandlers>;
}
export const useContextMenuSystemInfoHandlers = ({ hubs, outCommand }: UseContextMenuSystemHandlersProps) => {
export const useContextMenuSystemInfoHandlers = ({ hubs, outCommand, mapRef }: UseContextMenuSystemHandlersProps) => {
const contextMenuRef = useRef<ContextMenu | null>(null);
const [system, setSystem] = useState<string>();
const routeRef = useRef<(SolarSystemStaticInfoRaw | undefined)[]>([]);
const ref = useRef({ hubs, system, outCommand });
ref.current = { hubs, system, outCommand };
const ref = useRef({ hubs, system, outCommand, mapRef });
ref.current = { hubs, system, outCommand, mapRef };
const open = useCallback(
(ev: React.SyntheticEvent, systemId: string, route: (SolarSystemStaticInfoRaw | undefined)[]) => {
@@ -48,7 +48,7 @@ export const useContextMenuSystemInfoHandlers = ({ hubs, outCommand }: UseContex
}, []);
const onAddSystem = useCallback(() => {
const { system: solarSystemId, outCommand } = ref.current;
const { system: solarSystemId, outCommand, mapRef } = ref.current;
if (!solarSystemId) {
return;
}
@@ -60,11 +60,7 @@ export const useContextMenuSystemInfoHandlers = ({ hubs, outCommand }: UseContex
},
});
setTimeout(() => {
emitMapEvent({
name: Commands.centerSystem,
data: solarSystemId,
});
mapRef.current?.command(Commands.centerSystem, solarSystemId);
setSystem(undefined);
}, 200);
}, []);

View File

@@ -9,22 +9,13 @@ import { PrimeIcons } from 'primereact/api';
export interface FastSystemActionsProps {
systemId: string;
systemName: string;
regionName: string;
isWH: boolean;
showEdit?: boolean;
onOpenSettings(): void;
}
export const FastSystemActions = ({
systemId,
systemName,
regionName,
isWH,
onOpenSettings,
showEdit,
}: FastSystemActionsProps) => {
const ref = useRef({ systemId, systemName, regionName, isWH });
ref.current = { systemId, systemName, regionName, isWH };
export const FastSystemActions = ({ systemId, systemName, onOpenSettings, showEdit }: FastSystemActionsProps) => {
const ref = useRef({ systemId, systemName });
ref.current = { systemId, systemName };
const handleOpenZKB = useCallback(
() => window.open(`https://zkillboard.com/system/${ref.current.systemId}`, '_blank'),
@@ -36,17 +27,10 @@ export const FastSystemActions = ({
[],
);
const handleOpenDotlan = useCallback(() => {
if (ref.current.isWH) {
window.open(`https://evemaps.dotlan.net/system/${ref.current.systemName}`, '_blank');
return;
}
return window.open(
`https://evemaps.dotlan.net/map/${ref.current.regionName.replace(/ /gim, '_')}/${ref.current.systemName}#jumps`,
'_blank',
);
}, []);
const handleOpenDotlan = useCallback(
() => window.open(`https://evemaps.dotlan.net/system/${ref.current.systemName}`, '_blank'),
[],
);
const copySystemNameToClipboard = useCallback(async () => {
try {
@@ -59,9 +43,9 @@ export const FastSystemActions = ({
return (
<LayoutEventBlocker className={clsx('flex px-2 gap-2 justify-between items-center h-full')}>
<div className={clsx('flex gap-2 items-center h-full', classes.Links)}>
<WdImgButton tooltip={{ content: 'Open zkillboard' }} source={ZKB_ICON} onClick={handleOpenZKB} />
<WdImgButton tooltip={{ content: 'Open Anoikis' }} source={ANOIK_ICON} onClick={handleOpenAnoikis} />
<WdImgButton tooltip={{ content: 'Open Dotlan' }} source={DOTLAN_ICON} onClick={handleOpenDotlan} />
<WdImgButton source={ZKB_ICON} onClick={handleOpenZKB} />
<WdImgButton source={ANOIK_ICON} onClick={handleOpenAnoikis} />
<WdImgButton source={DOTLAN_ICON} onClick={handleOpenDotlan} />
</div>
<div className="flex gap-2 items-center pl-1">

View File

@@ -1,11 +1,4 @@
.MapRoot {
width: 100%;
height: 100%;
background-color: var(--rf-bg-color, #0C0A09);
&.BackgroundAlternateColor {
background-color: var(--rf-soft-bg-color, #171717);
--rf-node-bg-color: var(--rf-node-soft-bg-color, #202020);
}
}

View File

@@ -1,39 +1,43 @@
import { ForwardedRef, forwardRef, MouseEvent, useCallback, useEffect, useMemo } from 'react';
import { ForwardedRef, forwardRef, MouseEvent, useCallback, useEffect, useRef } from 'react';
import ReactFlow, {
Background,
ConnectionMode,
Edge,
EdgeChange,
MiniMap,
Node,
NodeChange,
NodeDragHandler,
OnConnect,
OnMoveEnd,
OnSelectionChangeFunc,
SelectionDragHandler,
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 { useEdgesState, useMapHandlers, useNodesState, useUpdateNodes } from './hooks';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useMapHandlers, useUpdateNodes } from './hooks';
import { MapHandlers, OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts';
import {
ContextMenuConnection,
ContextMenuRoot,
SolarSystemEdge,
SolarSystemNode,
useContextMenuConnectionHandlers,
useContextMenuRootHandlers,
} from './components';
import { getBehaviorForTheme } from './helpers/getThemeBehavior';
import { OnMapAddSystemCallback, OnMapSelectionChange } from './map.types';
import { OnMapSelectionChange } from './map.types';
import { SESSION_KEY } from '@/hooks/Mapper/constants.ts';
import { SolarSystemConnection, SolarSystemRawType } from '@/hooks/Mapper/types';
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
import { NodeSelectionMouseHandler } from '@/hooks/Mapper/components/contexts/types.ts';
import clsx from 'clsx';
import { useBackgroundVars } from './hooks/useBackgroundVars';
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
const DEFAULT_VIEW_PORT = { zoom: 1, x: 0, y: 0 };
@@ -75,6 +79,12 @@ const initialEdges = [
},
];
const nodeTypes = {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
custom: SolarSystemNode,
} as never;
const edgeTypes = {
floating: SolarSystemEdge,
};
@@ -83,19 +93,12 @@ interface MapCompProps {
refn: ForwardedRef<MapHandlers>;
onCommand: OutCommandHandler;
onSelectionChange: OnMapSelectionChange;
onManualDelete(systems: string[]): void;
canRemoveConnection?(connectionId: string): boolean;
onConnectionInfoClick?(e: SolarSystemConnection): void;
onAddSystem?: OnMapAddSystemCallback;
onSelectionContextMenu?: NodeSelectionMouseHandler;
minimapClasses?: string;
isShowMinimap?: boolean;
onSystemContextMenu: (event: MouseEvent<Element>, systemId: string) => void;
showKSpaceBG?: boolean;
isThickConnections?: boolean;
isShowBackgroundPattern?: boolean;
isSoftBackground?: boolean;
theme?: string;
}
const MapComp = ({
@@ -106,34 +109,26 @@ const MapComp = ({
onSystemContextMenu,
onConnectionInfoClick,
onSelectionContextMenu,
onManualDelete,
isShowMinimap,
showKSpaceBG,
isThickConnections,
isShowBackgroundPattern,
isSoftBackground,
theme,
onAddSystem,
canRemoveConnection,
}: MapCompProps) => {
const { getEdge, getNode, getNodes } = useReactFlow();
const [nodes, , onNodesChange] = useNodesState<Node<SolarSystemRawType>>(initialNodes);
const [edges, , onEdgesChange] = useEdgesState<Edge<SolarSystemConnection>>(initialEdges);
const { getNode } = useReactFlow();
const [nodes, , onNodesChange] = useNodesState<SolarSystemRawType>(initialNodes);
const [edges, , onEdgesChange] = useEdgesState<Edge<SolarSystemConnection>[]>(initialEdges);
useMapHandlers(refn, onSelectionChange);
useUpdateNodes(nodes);
const { handleRootContext, ...rootCtxProps } = useContextMenuRootHandlers({ onAddSystem });
const { handleRootContext, ...rootCtxProps } = useContextMenuRootHandlers();
const { handleConnectionContext, ...connectionCtxProps } = useContextMenuConnectionHandlers();
const { update } = useMapState();
const { variant, gap, size, color } = useBackgroundVars(theme);
const { isPanAndDrag, nodeComponent, connectionMode } = getBehaviorForTheme(theme || 'default');
const {
data: { systems },
} = useMapRootState();
// You can create nodeTypes dynamically based on the node component
const nodeTypes = useMemo(() => {
return {
custom: nodeComponent,
};
}, [nodeComponent]);
const { deleteSystems } = useDeleteSystems();
const systemsRef = useRef({ systems });
systemsRef.current = { systems };
const onConnect: OnConnect = useCallback(
params => {
@@ -191,97 +186,51 @@ const MapComp = ({
const handleNodesChange = useCallback(
(changes: NodeChange[]) => {
const systemsIdsToRemove: string[] = [];
// prevents single node deselection on background / same node click
// allows deseletion of all nodes if multiple are currently selected
if (changes.length === 1 && changes[0].type == 'select' && changes[0].selected === false) {
changes[0].selected = getNodes().filter(node => node.selected).length === 1;
}
const nextChanges = changes.reduce((acc, change) => {
if (change.type !== 'remove') {
return [...acc, change];
}
const node = getNode(change.id);
if (!node) {
return [...acc, change];
}
if (node.data.locked) {
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;
}
systemsIdsToRemove.push(node.data.id);
return [...acc, change];
}, [] as NodeChange[]);
if (systemsIdsToRemove.length > 0) {
onManualDelete(systemsIdsToRemove);
if (systemsIdsToRemove.length) {
deleteSystems(systemsIdsToRemove);
}
onNodesChange(nextChanges);
},
[getNode, getNodes, onManualDelete, onNodesChange],
);
const handleEdgesChange = useCallback(
(changes: EdgeChange[]) => {
const nextChanges = changes.reduce((acc, change) => {
if (change.type !== 'remove') {
return [...acc, change];
}
if (canRemoveConnection?.(change.id)) {
return [...acc, change];
}
const edge = getEdge(change.id);
if (!edge) {
return [...acc, change];
}
const sourceNode = getNode(edge.source);
const targetNode = getNode(edge.target);
if (!sourceNode || !targetNode) {
return [...acc, change];
}
if (sourceNode.data.locked || targetNode.data.locked) {
return acc;
}
return [...acc, change];
}, [] as EdgeChange[]);
onEdgesChange(nextChanges);
},
[getEdge, getNode, onEdgesChange],
[deleteSystems, getNode, onNodesChange],
);
useEffect(() => {
update(x => ({
...x,
showKSpaceBG: showKSpaceBG,
isThickConnections: isThickConnections,
}));
}, [showKSpaceBG, isThickConnections, update]);
}, [showKSpaceBG, update]);
return (
<>
<div className={clsx(classes.MapRoot, { [classes.BackgroundAlternateColor]: isSoftBackground })}>
<div className={classes.MapRoot}>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={handleNodesChange}
onEdgesChange={handleEdgesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
// TODO we need save into session all of this
// and on any action do either
defaultViewport={getViewPortFromStore()}
edgeTypes={edgeTypes}
nodeTypes={nodeTypes}
connectionMode={connectionMode}
connectionMode={ConnectionMode.Loose}
snapToGrid
nodeDragThreshold={10}
onNodeDragStop={handleDragStop}
@@ -289,11 +238,6 @@ const MapComp = ({
onConnectStart={() => update({ isConnecting: true })}
onConnectEnd={() => update({ isConnecting: false })}
onNodeMouseEnter={(_, node) => update({ hoverNodeId: node.id })}
onPaneClick={event => {
event.preventDefault();
event.stopPropagation();
}}
// onKeyUp=
onNodeMouseLeave={() => update({ hoverNodeId: null })}
onEdgeClick={(_, t) => {
onConnectionInfoClick?.(t.data);
@@ -314,19 +258,13 @@ const MapComp = ({
maxZoom={1.5}
elevateNodesOnSelect
deleteKeyCode={['Delete']}
{...(isPanAndDrag
? {
selectionOnDrag: true,
panOnDrag: [2],
}
: {})}
// 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
selectionMode={SelectionMode.Partial}
>
{isShowMinimap && <MiniMap pannable zoomable ariaLabel="Mini map" className={minimapClasses} />}
{isShowBackgroundPattern && <Background variant={variant} gap={gap} size={size} color={color} />}
<Background />
</ReactFlow>
{/* <button className="z-auto btn btn-primary absolute top-20 right-20" onClick={handleGetPassages}>
Test // DON NOT REMOVE

View File

@@ -8,8 +8,6 @@ export type MapData = MapUnionTypes & {
hoverNodeId: string | null;
visibleNodes: Set<string>;
showKSpaceBG: boolean;
isThickConnections: boolean;
linkedSigEveId: string;
};
interface MapProviderProps {
@@ -19,7 +17,6 @@ interface MapProviderProps {
const INITIAL_DATA: MapData = {
wormholesData: {},
wormholes: [],
effects: {},
characters: [],
userCharacters: [],
@@ -32,8 +29,6 @@ const INITIAL_DATA: MapData = {
hoverNodeId: null,
visibleNodes: new Set(),
showKSpaceBG: false,
isThickConnections: false,
userPermissions: {},
};
export interface MapContextProps {

View File

@@ -1,17 +1,15 @@
@import '@/hooks/Mapper/components/map/styles/eve-common-variables';
.ConnectionTimeEOL {
background-image: linear-gradient(207deg, transparent, var(--conn-time-eol));
background-image: linear-gradient(207deg, transparent, #7452c3e3);
}
.ConnectionFrigate {
background-image: linear-gradient(207deg, transparent, var(--conn-frigate));
background-image: linear-gradient(207deg, transparent, #325d88);
}
.ConnectionSave {
background-image: linear-gradient(207deg, transparent, var(--conn-save));
background-image: linear-gradient(207deg, transparent, rgba(155, 102, 45, 0.85));
}
.SelectedItem {
background-color: var(--selected-item-bg);
background-color: rgba(98, 98, 98, 0.33);
}

View File

@@ -3,17 +3,10 @@ import { ContextMenu } from 'primereact/contextmenu';
import { PrimeIcons } from 'primereact/api';
import { MenuItem } from 'primereact/menuitem';
import { Edge } from '@reactflow/core/dist/esm/types/edges';
import { ConnectionType, MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
import { MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
import clsx from 'clsx';
import classes from './ContextMenuConnection.module.scss';
import {
MASS_STATE_NAMES,
MASS_STATE_NAMES_ORDER,
SHIP_SIZES_NAMES,
SHIP_SIZES_NAMES_ORDER,
SHIP_SIZES_NAMES_SHORT,
SHIP_SIZES_SIZE,
} from '@/hooks/Mapper/components/map/constants.ts';
import { MASS_STATE_NAMES, MASS_STATE_NAMES_ORDER } from '@/hooks/Mapper/components/map/constants.ts';
export interface ContextMenuConnectionProps {
contextMenuRef: RefObject<ContextMenu>;
@@ -42,72 +35,46 @@ export const ContextMenuConnection: React.FC<ContextMenuConnectionProps> = ({
}
const isFrigateSize = edge.data?.ship_size_type === ShipSizeStatus.small;
const isWormhole = edge.data?.type !== ConnectionType.gate;
return [
...(isWormhole
{
label: `EOL`,
className: clsx({
[classes.ConnectionTimeEOL]: edge.data?.time_status === TimeStatus.eol,
}),
icon: PrimeIcons.CLOCK,
command: onChangeTimeState,
},
{
label: `Frigate`,
className: clsx({
[classes.ConnectionFrigate]: isFrigateSize,
}),
icon: PrimeIcons.CLOUD,
command: () =>
onChangeShipSizeStatus(
edge.data?.ship_size_type === ShipSizeStatus.small ? ShipSizeStatus.normal : ShipSizeStatus.small,
),
},
{
label: `Save mass`,
className: clsx({
[classes.ConnectionSave]: edge.data?.locked,
}),
icon: PrimeIcons.LOCK,
command: () => onToggleMassSave(!edge.data?.locked),
},
...(!isFrigateSize
? [
{
label: `EOL`,
className: clsx({
[classes.ConnectionTimeEOL]: edge.data?.time_status === TimeStatus.eol,
}),
icon: PrimeIcons.CLOCK,
command: onChangeTimeState,
},
{
label: `Frigate`,
className: clsx({
[classes.ConnectionFrigate]: isFrigateSize,
}),
icon: PrimeIcons.CLOUD,
command: () =>
onChangeShipSizeStatus(
edge.data?.ship_size_type === ShipSizeStatus.small ? ShipSizeStatus.large : ShipSizeStatus.small,
),
},
{
label: `Save mass`,
className: clsx({
[classes.ConnectionSave]: edge.data?.locked,
}),
icon: PrimeIcons.LOCK,
command: () => onToggleMassSave(!edge.data?.locked),
},
...(!isFrigateSize
? [
{
label: `Mass status`,
icon: PrimeIcons.CHART_PIE,
items: MASS_STATE_NAMES_ORDER.map(x => ({
label: MASS_STATE_NAMES[x],
className: clsx({
[classes.SelectedItem]: edge.data?.mass_status === x,
}),
command: () => onChangeMassState(x),
})),
},
]
: []),
{
label: `Ship Size`,
icon: PrimeIcons.CLOUD,
items: SHIP_SIZES_NAMES_ORDER.map(x => ({
label: (
<div className="grid grid-cols-[20px_120px_1fr_40px] gap-2 items-center">
<div className="text-[12px] font-bold text-stone-400">{SHIP_SIZES_NAMES_SHORT[x]}</div>
<div>{SHIP_SIZES_NAMES[x]}</div>
<div></div>
<div className="flex justify-end whitespace-nowrap text-[12px] font-bold text-stone-500">
{SHIP_SIZES_SIZE[x]} t.
</div>
</div>
) as unknown as string, // TODO my lovely kostyl
label: `Mass status`,
icon: PrimeIcons.CHART_PIE,
items: MASS_STATE_NAMES_ORDER.map(x => ({
label: MASS_STATE_NAMES[x],
className: clsx({
[classes.SelectedItem]: edge.data?.ship_size_type === x,
[classes.SelectedItem]: edge.data?.mass_status === x,
}),
command: () => onChangeShipSizeStatus(x),
command: () => onChangeMassState(x),
})),
},
]
@@ -118,7 +85,7 @@ export const ContextMenuConnection: React.FC<ContextMenuConnectionProps> = ({
command: onDeleteConnection,
},
];
}, [edge, onChangeTimeState, onDeleteConnection, onChangeShipSizeStatus, onToggleMassSave, onChangeMassState]);
}, [edge, onChangeTimeState, onDeleteConnection, onChangeMassState, onChangeShipSizeStatus]);
return (
<>

View File

@@ -4,7 +4,7 @@ import { ContextMenu } from 'primereact/contextmenu';
import { useMapState } from '../../MapProvider.tsx';
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
import { Edge } from '@reactflow/core/dist/esm/types/edges';
import { ConnectionType, MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
import { MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
export const useContextMenuConnectionHandlers = () => {
@@ -47,23 +47,6 @@ export const useContextMenuConnectionHandlers = () => {
setEdge(undefined);
};
const onChangeType = useCallback((type: ConnectionType) => {
const { edge, outCommand } = ref.current;
if (!edge) {
return;
}
outCommand({
type: OutCommand.updateConnectionType,
data: {
source: edge.source,
target: edge.target,
value: type,
},
});
}, []);
const onChangeMassState = useCallback((status: MassState) => {
const { edge, outCommand } = ref.current;
@@ -97,16 +80,14 @@ export const useContextMenuConnectionHandlers = () => {
},
});
if (status === ShipSizeStatus.small) {
outCommand({
type: OutCommand.updateConnectionMassStatus,
data: {
source: edge.source,
target: edge.target,
value: MassState.normal,
},
});
}
outCommand({
type: OutCommand.updateConnectionMassStatus,
data: {
source: edge.source,
target: edge.target,
value: MassState.normal,
},
});
}, []);
const onToggleMassSave = useCallback((locked: boolean) => {
@@ -137,7 +118,6 @@ export const useContextMenuConnectionHandlers = () => {
contextMenuRef,
onDeleteConnection,
onChangeTimeState,
onChangeType,
onChangeMassState,
onChangeShipSizeStatus,
onToggleMassSave,

View File

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

View File

@@ -1,55 +1,27 @@
@import '@/hooks/Mapper/components/map/styles/eve-common-variables';
@import "@/hooks/Mapper/components/map/styles/neon-variables";
.EdgePathBack {
fill: none;
stroke: #80a5c5;
stroke-width: 3px;
&.TimeCrit {
stroke: #f11ab2;
stroke-width: 4px;
}
&.Hovered {
stroke: #b5c8d9;
&.TimeCrit {
stroke: #ef7dce;
}
}
&.Tick {
stroke-width: 5px;
&.TimeCrit {
stroke-width: 6px;
}
}
&.Gate {
stroke: #9aff40;
.react-flow__edge.selected {
.EdgePathBack {
stroke: $pastel-yellow;
}
}
.EdgePathFront {
fill: none;
stroke: #2c3844;
stroke-width: 2px;
&.MassVerge:not(&.Frigate) {
stroke: #af0000;
stroke: #af2900;
}
&.MassHalf:not(&.Frigate) {
stroke: #ffd700;
stroke: #a85f00;
}
&.Frigate {
stroke: #d4f0ff;
}
&.Gate {
stroke: #1c1e15;
stroke: #4e62c9;
}
&.Hovered {
@@ -65,15 +37,28 @@
}
&.Frigate {
stroke: #d4f0ff;
stroke: #41acd7;
}
}
}
.EdgePathBack {
fill: none;
stroke: #80a5c5;
stroke-width: 3px;
&.TimeCrit {
stroke: #f11ab2;
stroke-width: 4px;
}
&.Tick {
stroke-width: 3px;
&.Hovered {
stroke: #b5c8d9;
&.Hovered {
stroke-width: 3px;
&.TimeCrit {
stroke: #ef7dce;
}
}
}
@@ -84,23 +69,7 @@
stroke-width: 8px;
}
.Handle {
border: 1px solid var(--pastel-blue);
width: 5px;
height: 5px;
z-index: 1001;
&.Tick {
width: 7px;
height: 7px;
}
&.Right {
margin-left: 0px;
}
}
.LinkLabel {
.LinkLabel{
font-size: 9px;
line-height: 10px;
padding: 2px 4px;
@@ -116,3 +85,13 @@
height: 8px;
font-size: 8px;
}
.Handle {
min-width: initial;
min-height: initial;
border: 1px solid #5a7d9a;
width: 5px;
height: 5px;
z-index: 1001;
}

View File

@@ -1,73 +1,39 @@
import { useCallback, useMemo, useState } from 'react';
import classes from './SolarSystemEdge.module.scss';
import { EdgeLabelRenderer, EdgeProps, getBezierPath, getSmoothStepPath, Position, useStore } from 'reactflow';
import { EdgeLabelRenderer, EdgeProps, getBezierPath, Position, useStore } from 'reactflow';
import { getEdgeParams } from '@/hooks/Mapper/components/map/utils.ts';
import clsx from 'clsx';
import { ConnectionType, MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
import { MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
import { PrimeIcons } from 'primereact/api';
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
import { SHIP_SIZES_DESCRIPTION, SHIP_SIZES_NAMES_SHORT } from '@/hooks/Mapper/components/map/constants.ts';
const MAP_TRANSLATES: Record<string, string> = {
[Position.Top]: 'translate(-48%, 0%)',
[Position.Top]: 'translate(-50%, 0%)',
[Position.Bottom]: 'translate(-50%, -100%)',
[Position.Left]: 'translate(0%, -50%)',
[Position.Right]: 'translate(-100%, -50%)',
};
const MAP_OFFSETS_TICK: Record<string, { x: number; y: number }> = {
[Position.Top]: { x: 0, y: 3 },
[Position.Bottom]: { x: 0, y: -3 },
[Position.Left]: { x: 3, y: 0 },
[Position.Right]: { x: -3, y: 0 },
};
const MAP_OFFSETS: Record<string, { x: number; y: number }> = {
[Position.Top]: { x: 0, y: 0 },
[Position.Bottom]: { x: 0, y: 0 },
[Position.Left]: { x: 0, y: 0 },
[Position.Right]: { x: 0, y: 0 },
};
export const SHIP_SIZES_COLORS = {
[ShipSizeStatus.small]: 'bg-indigo-400',
[ShipSizeStatus.medium]: 'bg-cyan-500',
[ShipSizeStatus.large]: '',
[ShipSizeStatus.freight]: 'bg-lime-400',
[ShipSizeStatus.capital]: 'bg-red-400',
};
export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }: EdgeProps<SolarSystemConnection>) => {
const sourceNode = useStore(useCallback(store => store.nodeInternals.get(source), [source]));
const targetNode = useStore(useCallback(store => store.nodeInternals.get(target), [target]));
const isWormhole = data?.type !== ConnectionType.gate;
const {
data: { isThickConnections },
} = useMapState();
const [hovered, setHovered] = useState(false);
const [path, labelX, labelY, sx, sy, tx, ty, sourcePos, targetPos] = useMemo(() => {
const { sx, sy, tx, ty, sourcePos, targetPos } = getEdgeParams(sourceNode, targetNode);
const offset = isThickConnections ? MAP_OFFSETS_TICK[targetPos] : MAP_OFFSETS[targetPos];
const method = isWormhole ? getBezierPath : getSmoothStepPath;
const [edgePath, labelX, labelY] = method({
sourceX: sx - offset.x,
sourceY: sy - offset.y,
const [edgePath, labelX, labelY] = getBezierPath({
sourceX: sx,
sourceY: sy,
sourcePosition: sourcePos,
targetPosition: targetPos,
targetX: tx + offset.x,
targetY: ty + offset.y,
targetX: tx,
targetY: ty,
});
return [edgePath, labelX, labelY, sx, sy, tx, ty, sourcePos, targetPos];
}, [isThickConnections, sourceNode, targetNode, isWormhole]);
}, [sourceNode, targetNode]);
if (!sourceNode || !targetNode || !data) {
return null;
@@ -78,10 +44,8 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
<path
id={`back_${id}`}
className={clsx(classes.EdgePathBack, {
[classes.Tick]: isThickConnections,
[classes.TimeCrit]: isWormhole && data.time_status === TimeStatus.eol,
[classes.TimeCrit]: data.time_status === TimeStatus.eol,
[classes.Hovered]: hovered,
[classes.Gate]: !isWormhole,
})}
d={path}
markerEnd={markerEnd}
@@ -90,12 +54,10 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
<path
id={`front_${id}`}
className={clsx(classes.EdgePathFront, {
[classes.Tick]: isThickConnections,
[classes.Hovered]: hovered,
[classes.MassVerge]: isWormhole && data.mass_status === MassState.verge,
[classes.MassHalf]: isWormhole && data.mass_status === MassState.half,
[classes.Frigate]: isWormhole && data.ship_size_type === ShipSizeStatus.small,
[classes.Gate]: !isWormhole,
[classes.MassVerge]: data.mass_status === MassState.verge,
[classes.MassHalf]: data.mass_status === MassState.half,
[classes.Frigate]: data.ship_size_type === ShipSizeStatus.small,
})}
d={path}
markerEnd={markerEnd}
@@ -113,29 +75,21 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
<EdgeLabelRenderer>
<div
className={clsx(
classes.Handle,
{ [classes.Tick]: isThickConnections, [classes.Right]: Position.Right === sourcePos },
'react-flow__handle absolute nodrag pointer-events-none',
)}
className={clsx(classes.Handle, 'react-flow__handle absolute nodrag pointer-events-none')}
style={{ transform: `${MAP_TRANSLATES[sourcePos]} translate(${sx}px,${sy}px)` }}
/>
<div
className={clsx(
classes.Handle,
{ [classes.Tick]: isThickConnections },
'react-flow__handle absolute nodrag pointer-events-none',
)}
className={clsx(classes.Handle, 'react-flow__handle absolute nodrag pointer-events-none')}
style={{ transform: `${MAP_TRANSLATES[targetPos]} translate(${tx}px,${ty}px)` }}
/>
<div
className="absolute flex items-center gap-1 pointer-events-none"
className="absolute flex items-center gap-1"
style={{
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
}}
>
{isWormhole && data.locked && (
{data.locked && (
<WdTooltipWrapper
content="Save mass"
className={clsx(
@@ -146,19 +100,6 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
<span className={clsx(PrimeIcons.LOCK, classes.icon)} />
</WdTooltipWrapper>
)}
{isWormhole && data.ship_size_type !== ShipSizeStatus.large && (
<WdTooltipWrapper
content={SHIP_SIZES_DESCRIPTION[data.ship_size_type]}
className={clsx(
classes.LinkLabel,
'pointer-events-auto rounded opacity-100 cursor-auto text-neutral-900 font-bold',
SHIP_SIZES_COLORS[data.ship_size_type],
)}
>
{SHIP_SIZES_NAMES_SHORT[data.ship_size_type]}
</WdTooltipWrapper>
)}
</div>
</EdgeLabelRenderer>
</>

View File

@@ -6,14 +6,7 @@ $pastel-green: #88b04b;
$pastel-yellow: #ffdd59;
$dark-bg: #2d2d2d;
$text-color: #ffffff;
$tooltip-bg: #202020;
$node-bg-color: #202020;
$node-soft-bg-color: #202020;
$text-color: #ffffff;
$tag-color: #38BDF8;
$region-name: #D6D3D1;
$custom-name: #93C5FD;
$tooltip-bg: #202020; // Темный фон для подсказок
.RootCustomNode {
display: flex;
@@ -92,7 +85,7 @@ $custom-name: #93C5FD;
box-shadow: 0 0 10px #9a1af1c2;
}
&.tooltip {
.tooltip {
background-color: $tooltip-bg;
color: $text-color;
padding: 5px 10px;
@@ -101,66 +94,49 @@ $custom-name: #93C5FD;
}
&.eve-system-status-home {
border: 1px solid var(--eve-solar-system-status-color-home-dark30);
background-image: linear-gradient(
275deg,
var(--eve-solar-system-status-home),
transparent
);
border: 1px solid darken($eve-solar-system-status-color-home, 30%);
background-image: linear-gradient(275deg, $eve-solar-system-status-friendly, transparent);
&.selected {
border-color: var(--eve-solar-system-status-color-home);
border-color: $eve-solar-system-status-color-home;
}
}
&.eve-system-status-friendly {
border: 1px solid var(--eve-solar-system-status-color-friendly-dark20);
background-image: linear-gradient(
275deg,
var(--eve-solar-system-status-friendly-dark30),
transparent
);
border: 1px solid darken($eve-solar-system-status-color-friendly, 20%);
background-image: linear-gradient(275deg, darken($eve-solar-system-status-friendly, 30%), transparent);
&.selected {
border-color: var(--eve-solar-system-status-color-friendly-dark5);
border-color: darken($eve-solar-system-status-color-friendly, 5%);
}
}
&.eve-system-status-lookingFor {
border: 1px solid var(--eve-solar-system-status-color-lookingFor-dark15);
border: 1px solid darken($eve-solar-system-status-color-lookingFor, 15%);
background-image: linear-gradient(275deg, #45ff8f2f, #457fff2f);
&.selected {
border-color: $pastel-pink;
}
}
&.eve-system-status-warning {
background-image: linear-gradient(
275deg,
var(--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(
275deg,
var(--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(
275deg,
var(--eve-solar-system-status-target),
transparent
);
background-image: linear-gradient(275deg, $eve-solar-system-status-target, transparent);
}
}
.Bookmarks {
position: absolute;
width: 100%;
z-index: 1;
z-index: 0;
display: flex;
left: 4px;
@@ -206,42 +182,6 @@ $custom-name: #93C5FD;
}
}
.Unsplashed {
position: absolute;
width: calc(50% - 4px);
z-index: -1;
display: flex;
flex-wrap: wrap;
gap: 2px;
left: 2px;
&--right {
left: calc(50% + 6px);
}
& > .Signature {
width: 13px;
height: 4px;
position: relative;
top: 3px;
border-radius: 5px;
color: #ffffff;
font-size: 8px;
text-align: center;
padding-top: 2px;
font-weight: bolder;
padding-left: 3px;
padding-right: 3px;
display: block;
background-color: #833ca4;
&:not(:first-child) {
box-shadow: inset 4px -3px 4px rgba(0, 0, 0, 0.3);
}
}
}
.icon {
width: 8px;
height: 8px;
@@ -266,18 +206,10 @@ $custom-name: #93C5FD;
.TagTitle {
font-size: 11px;
font-weight: medium;
font-weight: bold;
text-shadow: 0 0 2px rgba(231, 146, 52, 0.73);
color: var(--rf-tag-color, #38BDF8);
}
/* Firefox kostyl */
@-moz-document url-prefix() {
.classSystemName {
font-family: inherit !important;
font-weight: bold;
}
color: #ffb01d;
}
.classSystemName {
@@ -330,12 +262,6 @@ $custom-name: #93C5FD;
& > * {
line-height: 10px;
}
/* Firefox kostyl */
@-moz-document url-prefix() {
position: relative;
top: -1px;
}
}
.Handlers {
@@ -373,25 +299,4 @@ $custom-name: #93C5FD;
&.HandleLeft {
left: -2px;
}
&.Tick {
width: 7px;
height: 7px;
&.HandleTop {
top: -3px;
}
&.HandleRight {
right: -3px;
}
&.HandleBottom {
bottom: -3px;
}
&.HandleLeft {
left: -3px;
}
}
}

View File

@@ -0,0 +1,271 @@
import { memo, useMemo } from 'react';
import { Handle, Position, WrapNodeProps } from 'reactflow';
import { MapSolarSystemType } from '../../map.types';
import classes from './SolarSystemNode.module.scss';
import clsx from 'clsx';
import {
EFFECT_BACKGROUND_STYLES,
LABELS_INFO,
LABELS_ORDER,
MARKER_BOOKMARK_BG_STYLES,
STATUS_CLASSES,
} from '@/hooks/Mapper/components/map/constants.ts';
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace.ts';
import { WormholeClassComp } from '@/hooks/Mapper/components/map/components/WormholeClassComp';
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
import { getSystemClassStyles } from '@/hooks/Mapper/components/map/helpers';
import { sortWHClasses } from '@/hooks/Mapper/helpers';
import { PrimeIcons } from 'primereact/api';
import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager.ts';
import { OutCommand } from '@/hooks/Mapper/types';
import { useDoubleClick } from '@/hooks/Mapper/hooks/useDoubleClick.ts';
import { REGIONS_MAP, Spaces } from '@/hooks/Mapper/constants';
const SpaceToClass: Record<string, string> = {
[Spaces.Caldari]: classes.Caldaria,
[Spaces.Matar]: classes.Mataria,
[Spaces.Amarr]: classes.Amarria,
[Spaces.Gallente]: classes.Gallente,
};
const sortedLabels = (labels: string[]) => {
if (!labels) {
return [];
}
return LABELS_ORDER.filter(x => labels.includes(x)).map(x => LABELS_INFO[x]);
};
export const getActivityType = (count: number) => {
if (count <= 5) {
return 'activityNormal';
}
if (count <= 30) {
return 'activityWarn';
}
return 'activityDanger';
};
// eslint-disable-next-line react/display-name
export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarSystemType>) => {
const {
system_class,
security,
class_title,
solar_system_id,
statics,
effect_name,
region_name,
region_id,
is_shattered,
solar_system_name,
} = data.system_static_info;
const { locked, name, tag, status, labels, id } = data || {};
const customName = solar_system_name !== name ? name : undefined;
const {
data: {
characters,
presentCharacters,
wormholesData,
hubs,
kills,
userCharacters,
isConnecting,
hoverNodeId,
visibleNodes,
showKSpaceBG,
},
outCommand,
} = useMapState();
const visible = useMemo(() => visibleNodes.has(id), [id, visibleNodes]);
const charactersInSystem = useMemo(() => {
return characters.filter(c => c.location?.solar_system_id === solar_system_id).filter(c => c.online);
// eslint-disable-next-line
}, [characters, presentCharacters, solar_system_id]);
const isWormhole = isWormholeSpace(system_class);
const classTitleColor = useMemo(
() => getSystemClassStyles({ systemClass: system_class, security }),
[security, system_class],
);
const sortedStatics = useMemo(() => sortWHClasses(wormholesData, statics), [wormholesData, statics]);
const lebM = useMemo(() => new LabelsManager(labels ?? ''), [labels]);
const labelsInfo = useMemo(() => sortedLabels(lebM.list), [lebM]);
const labelCustom = useMemo(() => lebM.customLabel, [lebM]);
const killsCount = useMemo(() => {
const systemKills = kills[solar_system_id];
if (!systemKills) {
return null;
}
return systemKills;
}, [kills, solar_system_id]);
const hasUserCharacters = useMemo(() => {
return charactersInSystem.some(x => userCharacters.includes(x.eve_id));
}, [charactersInSystem, userCharacters]);
const dbClick = useDoubleClick(() => {
outCommand({
type: OutCommand.openSettings,
data: {
system_id: solar_system_id.toString(),
},
});
});
const showHandlers = isConnecting || hoverNodeId === id;
const space = showKSpaceBG ? REGIONS_MAP[region_id] : '';
const regionClass = showKSpaceBG ? SpaceToClass[space] : null;
return (
<>
{visible && (
<div className={classes.Bookmarks}>
{labelCustom !== '' && (
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.custom)}>
<span className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] ">{labelCustom}</span>
</div>
)}
{is_shattered && (
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.shattered)}>
<span className={clsx('pi pi-chart-pie', classes.icon)} />
</div>
)}
{killsCount && (
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[getActivityType(killsCount)])}>
<div className={clsx(classes.BookmarkWithIcon)}>
<span className={clsx(PrimeIcons.BOLT, classes.icon)} />
<span className={clsx(classes.text)}>{killsCount}</span>
</div>
</div>
)}
{labelsInfo.map(x => (
<div key={x.id} className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[x.id])}>
{x.shortName}
</div>
))}
</div>
)}
<div
className={clsx(classes.RootCustomNode, regionClass, classes[STATUS_CLASSES[status]], {
[classes.selected]: selected,
})}
>
{visible && (
<>
<div className={classes.HeadRow}>
<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,
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] flex-grow overflow-hidden text-ellipsis whitespace-nowrap font-sans',
)}
>
{solar_system_name}
</div>
{isWormhole && (
<div className={classes.statics}>
{sortedStatics.map(x => (
<WormholeClassComp key={x} id={x} />
))}
</div>
)}
{effect_name !== null && isWormhole && (
<div className={clsx(classes.effect, EFFECT_BACKGROUND_STYLES[effect_name])}></div>
)}
</div>
<div className={clsx(classes.BottomRow, 'flex items-center justify-between')}>
{customName && (
<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-shadow:_0_1px_0_rgb(0_0_0_/_40%)] text-stone-300 whitespace-nowrap overflow-hidden text-ellipsis mr-0.5',
)}
>
{region_name}
</div>
)}
{isWormhole && !customName && <div />}
<div className="flex items-center justify-end">
<div className="flex gap-1 items-center">
{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', fontWeight: 'bold' }}></i>
)}
{charactersInSystem.length > 0 && (
<div className={clsx(classes.localCounter, { ['text-amber-300']: hasUserCharacters })}>
<i className="pi pi-users" style={{ fontSize: '0.50rem' }}></i>
<span className="font-sans">{charactersInSystem.length}</span>
</div>
)}
</div>
</div>
</div>
</>
)}
</div>
<div onMouseDownCapture={dbClick} className={classes.Handlers}>
<Handle
type="source"
className={clsx(classes.Handle, classes.HandleTop, { [classes.selected]: selected })}
style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
position={Position.Top}
id="a"
/>
<Handle
type="source"
className={clsx(classes.Handle, classes.HandleRight, { [classes.selected]: selected })}
style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
position={Position.Right}
id="b"
/>
<Handle
type="source"
className={clsx(classes.Handle, classes.HandleBottom, { [classes.selected]: selected })}
style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
position={Position.Bottom}
id="c"
/>
<Handle
type="source"
className={clsx(classes.Handle, classes.HandleLeft, { [classes.selected]: selected })}
style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
position={Position.Left}
id="d"
/>
</div>
</>
);
});

View File

@@ -1,209 +0,0 @@
import { memo } from 'react';
import { MapSolarSystemType } from '../../map.types';
import { Handle, Position, NodeProps } from 'reactflow';
import clsx from 'clsx';
import classes from './SolarSystemNodeDefault.module.scss';
import { PrimeIcons } from 'primereact/api';
import { useSolarSystemNode } from '../../hooks/useSolarSystemNode';
import {
MARKER_BOOKMARK_BG_STYLES,
STATUS_CLASSES,
EFFECT_BACKGROUND_STYLES,
} from '@/hooks/Mapper/components/map/constants';
import { WormholeClassComp } from '@/hooks/Mapper/components/map/components/WormholeClassComp';
import { UnsplashedSignature } from '@/hooks/Mapper/components/map/components/UnsplashedSignature';
export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>) => {
const nodeVars = useSolarSystemNode(props);
return (
<>
{nodeVars.visible && (
<div className={classes.Bookmarks}>
{nodeVars.labelCustom !== '' && (
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.custom)}>
<span className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] ">{nodeVars.labelCustom}</span>
</div>
)}
{nodeVars.isShattered && (
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.shattered)}>
<span className={clsx('pi pi-chart-pie', classes.icon)} />
</div>
)}
{nodeVars.killsCount && (
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[nodeVars.killsActivityType!])}>
<div className={clsx(classes.BookmarkWithIcon)}>
<span className={clsx(PrimeIcons.BOLT, classes.icon)} />
<span className={clsx(classes.text)}>{nodeVars.killsCount}</span>
</div>
</div>
)}
{nodeVars.labelsInfo.map(x => (
<div key={x.id} className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[x.id])}>
{x.shortName}
</div>
))}
</div>
)}
<div
className={clsx(
classes.RootCustomNode,
nodeVars.regionClass && classes[nodeVars.regionClass],
classes[STATUS_CLASSES[nodeVars.status]],
{
[classes.selected]: nodeVars.selected,
},
)}
>
{nodeVars.visible && (
<>
<div className={classes.HeadRow}>
<div
className={clsx(
classes.classTitle,
nodeVars.classTitleColor,
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]',
)}
>
{nodeVars.classTitle ?? '-'}
</div>
{nodeVars.tag != null && nodeVars.tag !== '' && (
<div className={clsx(classes.TagTitle, 'text-sky-400 font-medium')}>{nodeVars.tag}</div>
)}
<div
className={clsx(
classes.classSystemName,
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] flex-grow overflow-hidden text-ellipsis whitespace-nowrap font-sans',
)}
>
{nodeVars.systemName}
</div>
{nodeVars.isWormhole && (
<div className={classes.statics}>
{nodeVars.sortedStatics.map(whClass => (
<WormholeClassComp key={whClass} id={whClass} />
))}
</div>
)}
{nodeVars.effectName !== null && nodeVars.isWormhole && (
<div className={clsx(classes.effect, EFFECT_BACKGROUND_STYLES[nodeVars.effectName])} />
)}
</div>
<div className={clsx(classes.BottomRow, 'flex items-center justify-between')}>
{nodeVars.customName && (
<div className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] text-blue-300 whitespace-nowrap overflow-hidden text-ellipsis mr-0.5">
{nodeVars.customName}
</div>
)}
{!nodeVars.isWormhole && !nodeVars.customName && (
<div className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] text-stone-300 whitespace-nowrap overflow-hidden text-ellipsis mr-0.5">
{nodeVars.regionName}
</div>
)}
{nodeVars.isWormhole && !nodeVars.customName && <div />}
<div className="flex items-center justify-end">
<div className="flex gap-1 items-center">
{nodeVars.locked && (
<i className={PrimeIcons.LOCK} style={{ fontSize: '0.45rem', fontWeight: 'bold' }} />
)}
{nodeVars.hubs.includes(nodeVars.solarSystemId.toString()) && (
<i className={PrimeIcons.MAP_MARKER} style={{ fontSize: '0.45rem', fontWeight: 'bold' }} />
)}
{nodeVars.charactersInSystem.length > 0 && (
<div
className={clsx(classes.localCounter, {
['text-amber-300']: nodeVars.hasUserCharacters,
})}
>
<i className="pi pi-users" style={{ fontSize: '0.50rem' }} />
<span className="font-sans">{nodeVars.charactersInSystem.length}</span>
</div>
)}
</div>
</div>
</div>
</>
)}
</div>
{nodeVars.visible && (
<>
{nodeVars.unsplashedLeft.length > 0 && (
<div className={classes.Unsplashed}>
{nodeVars.unsplashedLeft.map(sig => (
<UnsplashedSignature key={sig.sig_id} signature={sig} />
))}
</div>
)}
{nodeVars.unsplashedRight.length > 0 && (
<div className={clsx(classes.Unsplashed, classes['Unsplashed--right'])}>
{nodeVars.unsplashedRight.map(sig => (
<UnsplashedSignature key={sig.sig_id} signature={sig} />
))}
</div>
)}
</>
)}
<div onMouseDownCapture={nodeVars.dbClick} className={classes.Handlers}>
<Handle
type="source"
className={clsx(classes.Handle, classes.HandleTop, {
[classes.selected]: nodeVars.selected,
[classes.Tick]: nodeVars.isThickConnections,
})}
style={{ visibility: nodeVars.showHandlers ? 'visible' : 'hidden' }}
position={Position.Top}
id="a"
/>
<Handle
type="source"
className={clsx(classes.Handle, classes.HandleRight, {
[classes.selected]: nodeVars.selected,
[classes.Tick]: nodeVars.isThickConnections,
})}
style={{ visibility: nodeVars.showHandlers ? 'visible' : 'hidden' }}
position={Position.Right}
id="b"
/>
<Handle
type="source"
className={clsx(classes.Handle, classes.HandleBottom, {
[classes.selected]: nodeVars.selected,
[classes.Tick]: nodeVars.isThickConnections,
})}
style={{ visibility: nodeVars.showHandlers ? 'visible' : 'hidden' }}
position={Position.Bottom}
id="c"
/>
<Handle
type="source"
className={clsx(classes.Handle, classes.HandleLeft, {
[classes.selected]: nodeVars.selected,
[classes.Tick]: nodeVars.isThickConnections,
})}
style={{ visibility: nodeVars.showHandlers ? 'visible' : 'hidden' }}
position={Position.Left}
id="d"
/>
</div>
</>
);
});
SolarSystemNodeDefault.displayName = 'SolarSystemNodeDefault';

View File

@@ -1,91 +0,0 @@
@import './SolarSystemNodeDefault.module.scss';
/* ---------------------------
Only override what's different
--------------------------- */
/* 1) .RootCustomNode:
- new background-color using CSS var
- plus color, font-family, and font-weight */
.RootCustomNode {
background-color: var(--rf-node-bg-color, #202020) !important;
color: var(--rf-text-color, #ffffff);
font-family: var(--rf-node-font-family, inherit) !important;
font-weight: var(--rf-node-font-weight, inherit) !important;
}
/* 2) .Bookmarks:
- add var-based font family/weight
*/
.Bookmarks {
font-family: var(--rf-node-font-family, inherit) !important;
font-weight: var(--rf-node-font-weight, inherit) !important;
}
/* 3) .HeadRow, .classTitle, .classSystemName:
- add new references to var-based font family/weight
*/
.HeadRow {
font-family: var(--rf-node-font-family, inherit) !important;
font-weight: var(--rf-node-font-weight, inherit) !important;
.classTitle {
font-family: var(--rf-node-font-family, inherit) !important;
font-weight: var(--rf-node-font-weight, inherit) !important;
}
@-moz-document url-prefix() {
.classSystemName {
font-family: var(--rf-node-font-family, inherit) !important;
font-weight: var(--rf-node-font-weight, inherit) !important;
}
}
.classSystemName {
font-family: var(--rf-node-font-family, inherit) !important;
font-weight: var(--rf-node-font-weight, inherit) !important;
}
}
/* 4) .BottomRow:
- introduces .tagTitle, .regionName, .customName, .localCounter
referencing new CSS variables */
.BottomRow {
font-family: var(--rf-node-font-family, inherit) !important;
font-weight: var(--rf-node-font-weight, inherit) !important;
.tagTitle {
font-size: 11px;
font-weight: medium;
text-shadow: 0 0 2px rgba(231, 146, 52, 0.73);
font-family: var(--rf-node-font-family, inherit) !important;
font-weight: var(--rf-node-font-weight, inherit) !important;
color: var(--rf-tag-color, #38BDF8);
}
.regionName {
color: var(--rf-region-name, #D6D3D1);
font-family: var(--rf-node-font-family, inherit) !important;
font-weight: var(--rf-node-font-weight, inherit) !important;
}
.customName {
color: var(--rf-custom-name, #93C5FD);
font-family: var(--rf-node-font-family, inherit) !important;
font-weight: var(--rf-node-font-weight, inherit) !important;
}
.localCounter {
display: flex;
color: var(--rf-has-user-characters, #fbbf24);
font-family: var(--rf-node-font-family, inherit) !important;
font-weight: var(--rf-node-font-weight, inherit) !important;
gap: 2px;
.hasUserCharacters {
color: var(--rf-has-user-characters, #fbbf24);
font-family: var(--rf-node-font-family, inherit) !important;
font-weight: var(--rf-node-font-weight, inherit) !important;
}
}
}

View File

@@ -1,219 +0,0 @@
import { memo } from 'react';
import { MapSolarSystemType } from '../../map.types';
import { Handle, Position, NodeProps } from 'reactflow';
import clsx from 'clsx';
import classes from './SolarSystemNodeTheme.module.scss';
import { PrimeIcons } from 'primereact/api';
import { useSolarSystemNode } from '../../hooks/useSolarSystemNode';
import {
MARKER_BOOKMARK_BG_STYLES,
STATUS_CLASSES,
EFFECT_BACKGROUND_STYLES,
} from '@/hooks/Mapper/components/map/constants';
import { WormholeClassComp } from '@/hooks/Mapper/components/map/components/WormholeClassComp';
import { UnsplashedSignature } from '@/hooks/Mapper/components/map/components/UnsplashedSignature';
export const SolarSystemNodeTheme = memo((props: NodeProps<MapSolarSystemType>) => {
const nodeVars = useSolarSystemNode(props);
return (
<>
{nodeVars.visible && (
<div className={classes.Bookmarks}>
{nodeVars.labelCustom !== '' && (
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.custom)}>
<span className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] ">{nodeVars.labelCustom}</span>
</div>
)}
{nodeVars.isShattered && (
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.shattered)}>
<span className={clsx('pi pi-chart-pie', classes.icon)} />
</div>
)}
{nodeVars.killsCount && (
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[nodeVars.killsActivityType!])}>
<div className={clsx(classes.BookmarkWithIcon)}>
<span className={clsx(PrimeIcons.BOLT, classes.icon)} />
<span className={clsx(classes.text)}>{nodeVars.killsCount}</span>
</div>
</div>
)}
{nodeVars.labelsInfo.map(x => (
<div key={x.id} className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[x.id])}>
{x.shortName}
</div>
))}
</div>
)}
<div
className={clsx(
classes.RootCustomNode,
nodeVars.regionClass && classes[nodeVars.regionClass],
classes[STATUS_CLASSES[nodeVars.status]],
{
[classes.selected]: nodeVars.selected,
},
)}
>
{nodeVars.visible && (
<>
<div className={classes.HeadRow}>
<div
className={clsx(
classes.classTitle,
nodeVars.classTitleColor,
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]',
)}
>
{nodeVars.classTitle ?? '-'}
</div>
{nodeVars.tag != null && nodeVars.tag !== '' && (
<div className={clsx(classes.TagTitle)}>{nodeVars.tag}</div>
)}
<div
className={clsx(
classes.classSystemName,
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] flex-grow overflow-hidden text-ellipsis whitespace-nowrap',
)}
>
{nodeVars.systemName}
</div>
{nodeVars.isWormhole && (
<div className={classes.statics}>
{nodeVars.sortedStatics.map(whClass => (
<WormholeClassComp key={whClass} id={whClass} />
))}
</div>
)}
{nodeVars.effectName !== null && nodeVars.isWormhole && (
<div className={clsx(classes.effect, EFFECT_BACKGROUND_STYLES[nodeVars.effectName])} />
)}
</div>
<div className={clsx(classes.BottomRow, 'flex items-center justify-between')}>
{nodeVars.customName && (
<div
className={clsx(
classes.CustomName,
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] whitespace-nowrap overflow-hidden text-ellipsis mr-0.5',
)}
>
{nodeVars.customName}
</div>
)}
{!nodeVars.isWormhole && !nodeVars.customName && (
<div
className={clsx(
classes.RegionName,
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] whitespace-nowrap overflow-hidden text-ellipsis mr-0.5',
)}
>
{nodeVars.regionName}
</div>
)}
{nodeVars.isWormhole && !nodeVars.customName && <div />}
<div className="flex items-center justify-end">
<div className="flex gap-1 items-center">
{nodeVars.locked && (
<i className={PrimeIcons.LOCK} style={{ fontSize: '0.45rem', fontWeight: 'bold' }} />
)}
{nodeVars.hubs.includes(nodeVars.solarSystemId.toString()) && (
<i className={PrimeIcons.MAP_MARKER} style={{ fontSize: '0.45rem', fontWeight: 'bold' }} />
)}
{nodeVars.charactersInSystem.length > 0 && (
<div
className={clsx(classes.localCounter, {
[classes.hasUserCharacters]: nodeVars.hasUserCharacters,
})}
>
<i className="pi pi-users" style={{ fontSize: '0.50rem' }} />
<span className="font-sans">{nodeVars.charactersInSystem.length}</span>
</div>
)}
</div>
</div>
</div>
</>
)}
</div>
{nodeVars.visible && (
<>
{nodeVars.unsplashedLeft.length > 0 && (
<div className={classes.Unsplashed}>
{nodeVars.unsplashedLeft.map(sig => (
<UnsplashedSignature key={sig.sig_id} signature={sig} />
))}
</div>
)}
{nodeVars.unsplashedRight.length > 0 && (
<div className={clsx(classes.Unsplashed, classes['Unsplashed--right'])}>
{nodeVars.unsplashedRight.map(sig => (
<UnsplashedSignature key={sig.sig_id} signature={sig} />
))}
</div>
)}
</>
)}
<div onMouseDownCapture={nodeVars.dbClick} className={classes.Handlers}>
<Handle
type="source"
className={clsx(classes.Handle, classes.HandleTop, {
[classes.selected]: nodeVars.selected,
[classes.Tick]: nodeVars.isThickConnections,
})}
style={{ visibility: nodeVars.showHandlers ? 'visible' : 'hidden' }}
position={Position.Top}
id="a"
/>
<Handle
type="source"
className={clsx(classes.Handle, classes.HandleRight, {
[classes.selected]: nodeVars.selected,
[classes.Tick]: nodeVars.isThickConnections,
})}
style={{ visibility: nodeVars.showHandlers ? 'visible' : 'hidden' }}
position={Position.Right}
id="b"
/>
<Handle
type="source"
className={clsx(classes.Handle, classes.HandleBottom, {
[classes.selected]: nodeVars.selected,
[classes.Tick]: nodeVars.isThickConnections,
})}
style={{ visibility: nodeVars.showHandlers ? 'visible' : 'hidden' }}
position={Position.Bottom}
id="c"
/>
<Handle
type="source"
className={clsx(classes.Handle, classes.HandleLeft, {
[classes.selected]: nodeVars.selected,
[classes.Tick]: nodeVars.isThickConnections,
})}
style={{ visibility: nodeVars.showHandlers ? 'visible' : 'hidden' }}
position={Position.Left}
id="d"
/>
</div>
</>
);
});
SolarSystemNodeTheme.displayName = 'SolarSystemNodeTheme';

View File

@@ -1,2 +1 @@
export * from './SolarSystemNodeDefault';
export * from './SolarSystemNodeTheme';
export * from './SolarSystemNode';

View File

@@ -1,18 +0,0 @@
@import '@/hooks/Mapper/components/map/styles/eve-common-variables';
.Signature {
position: relative;
top: 3px;
display: block;
& > .Box {
width: 13px;
height: 4px;
border-radius: 4px;
color: var(--text-color);
font-size: 8px;
text-align: center;
font-weight: bolder;
display: block;
}
}

View File

@@ -1,65 +0,0 @@
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
import { InfoDrawer } from '@/hooks/Mapper/components/ui-kit';
import classes from './UnsplashedSignature.module.scss';
import { SystemSignature } from '@/hooks/Mapper/types/signatures';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { WORMHOLE_CLASS_STYLES, WORMHOLES_ADDITIONAL_INFO } from '@/hooks/Mapper/components/map/constants.ts';
import { useMemo } from 'react';
import clsx from 'clsx';
import { renderInfoColumn } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders';
import { k162Types } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureK162TypeSelect';
interface UnsplashedSignatureProps {
signature: SystemSignature;
}
export const UnsplashedSignature = ({ signature }: UnsplashedSignatureProps) => {
const {
data: { wormholesData },
} = useMapRootState();
const whData = useMemo(() => wormholesData[signature.type], [signature.type, wormholesData]);
const whClass = useMemo(() => (whData ? WORMHOLES_ADDITIONAL_INFO[whData.dest] : null), [whData]);
const k162TypeOption = useMemo(() => {
if (!signature.custom_info) {
return null;
}
const customInfo = JSON.parse(signature.custom_info);
if (!customInfo.k162Type) {
return null;
}
return k162Types.find(x => x.value === customInfo.k162Type);
}, [signature]);
const whClassStyle = useMemo(() => {
if (signature.type === 'K162' && k162TypeOption) {
const k162Data = wormholesData[k162TypeOption.whClassName];
const k162Class = k162Data ? WORMHOLES_ADDITIONAL_INFO[k162Data.dest] : null;
return k162Class ? WORMHOLE_CLASS_STYLES[k162Class.wormholeClassID] : '';
}
return whClass ? WORMHOLE_CLASS_STYLES[whClass.wormholeClassID] : '';
}, [signature, whClass, k162TypeOption, wormholesData]);
return (
<WdTooltipWrapper
className={clsx(classes.Signature)}
content={
(
<div className="flex flex-col gap-1">
<InfoDrawer title={<b className="text-slate-50">{signature.eve_id}</b>}>
{renderInfoColumn(signature)}
</InfoDrawer>
</div>
) as React.ReactNode
}
>
<div className={clsx(classes.Box, whClassStyle)}>
<svg width="13" height="4" viewBox="0 0 13 4" xmlns="http://www.w3.org/2000/svg">
<rect width="13" height="4" rx="2" className={whClassStyle} fill="currentColor" />
</svg>
</div>
</WdTooltipWrapper>
);
};

View File

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

View File

@@ -1,4 +1,4 @@
import { ConnectionType, MassState, ShipSizeStatus } from '@/hooks/Mapper/types';
import { MassState } from '@/hooks/Mapper/types';
export enum SOLAR_SYSTEM_CLASS_IDS {
ccp1 = -1,
@@ -712,13 +712,6 @@ export const STATUS_CLASSES: Record<number, string> = {
[STATUSES.dangerous]: 'eve-system-status-dangerous',
};
export const TYPE_NAMES_ORDER = [ConnectionType.wormhole, ConnectionType.gate];
export const TYPE_NAMES = {
[ConnectionType.wormhole]: 'Wormhole',
[ConnectionType.gate]: 'Gate',
};
export const MASS_STATE_NAMES_ORDER = [MassState.verge, MassState.half, MassState.normal];
export const MASS_STATE_NAMES = {
@@ -727,41 +720,16 @@ export const MASS_STATE_NAMES = {
[MassState.verge]: 'Verge of collapse',
};
export const SHIP_SIZES_NAMES_ORDER = [
ShipSizeStatus.small,
ShipSizeStatus.medium,
ShipSizeStatus.large,
ShipSizeStatus.freight,
ShipSizeStatus.capital,
];
export const SHIP_SIZES_NAMES = {
[ShipSizeStatus.small]: 'Frigate',
[ShipSizeStatus.medium]: 'Medium',
[ShipSizeStatus.large]: 'Normal',
[ShipSizeStatus.freight]: 'Huge',
[ShipSizeStatus.capital]: 'Capital',
};
export const SHIP_SIZES_SIZE = {
[ShipSizeStatus.small]: '5K',
[ShipSizeStatus.medium]: '62K',
[ShipSizeStatus.large]: '375K',
[ShipSizeStatus.freight]: '1M',
[ShipSizeStatus.capital]: '2M',
};
export const SHIP_SIZES_DESCRIPTION = {
[ShipSizeStatus.small]: 'Frigate wormhole - up to Destroyer | 5K t.',
[ShipSizeStatus.medium]: 'Cruise wormhole - up to Battlecruiser | 62K t.',
[ShipSizeStatus.large]: 'Large wormhole - up to Battleship | 375K t.',
[ShipSizeStatus.freight]: 'Huge wormhole - up to Freighter | 1M t.',
[ShipSizeStatus.capital]: 'Capital wormhole - up to Capital | 2M t.',
};
export const SHIP_SIZES_NAMES_SHORT = {
[ShipSizeStatus.small]: 'S',
[ShipSizeStatus.medium]: 'M',
[ShipSizeStatus.large]: 'L',
[ShipSizeStatus.freight]: 'H',
[ShipSizeStatus.capital]: 'XL',
};
// export const SHIP_SIZES_NAMES_ORDER = [
// ShipSizeStatus.small,
// ShipSizeStatus.normal,
// // ShipSizeStatus.large,
// // ShipSizeStatus.capital,
// ];
//
// export const SHIP_SIZES_NAMES = {
// [ShipSizeStatus.small]: 'Frigate',
// [ShipSizeStatus.normal]: 'Normal',
// // [ShipSizeStatus.large]: 'Normal',
// // [ShipSizeStatus.capital]: 'Normal',
// };

View File

@@ -1,32 +0,0 @@
import { SolarSystemNodeDefault, SolarSystemNodeTheme } from '../components/SolarSystemNode';
import type { NodeProps } from 'reactflow';
import type { ComponentType } from 'react';
import { MapSolarSystemType } from '../map.types';
import { ConnectionMode } from 'reactflow';
export type SolarSystemNodeComponent = ComponentType<NodeProps<MapSolarSystemType>>;
interface ThemeBehavior {
isPanAndDrag: boolean;
nodeComponent: SolarSystemNodeComponent;
connectionMode: ConnectionMode;
}
const THEME_BEHAVIORS: {
[key: string]: ThemeBehavior;
} = {
default: {
isPanAndDrag: false,
nodeComponent: SolarSystemNodeDefault,
connectionMode: ConnectionMode.Loose,
},
pathfinder: {
isPanAndDrag: true,
nodeComponent: SolarSystemNodeTheme,
connectionMode: ConnectionMode.Loose,
},
};
export function getBehaviorForTheme(themeName: string) {
return THEME_BEHAVIORS[themeName] ?? THEME_BEHAVIORS.default;
}

View File

@@ -3,4 +3,3 @@ export * from './convertSystem2Node';
export * from './getSystemClassStyles';
export * from './getShapeClass';
export * from './getBackgroundClass';
export * from './prepareUnsplashedChunks';

View File

@@ -1,27 +0,0 @@
// Helper function to split an array into chunks of size
const chunkArray = (array: any[], size: number) => {
const chunks = [];
for (let i = 0; i < array.length; i += size) {
chunks.push(array.slice(i, i + size));
}
return chunks;
};
export const prepareUnsplashedChunks = (items: any[]) => {
// Split the items into chunks of 4
const chunks = chunkArray(items, 4);
// Get the column elements
const leftColumn: any[] = [];
const rightColumn: any[] = [];
chunks.forEach((chunk, index) => {
const column = index % 2 === 0 ? leftColumn : rightColumn;
chunk.forEach(item => {
column.push(item);
});
});
return [leftColumn, rightColumn];
};

View File

@@ -12,7 +12,6 @@ export const useMapAddSystems = () => {
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);
}, []);

View File

@@ -5,21 +5,24 @@ import { OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts
export const useMapRemoveSystems = (onSelectionChange: OnMapSelectionChange) => {
const rf = useReactFlow();
const ref = useRef({ onSelectionChange, rf });
ref.current = { onSelectionChange, rf };
const ref = useRef({ onSelectionChange });
ref.current = { onSelectionChange };
return useCallback((systems: CommandRemoveSystems) => {
ref.current.rf.deleteElements({ nodes: systems.map(x => ({ id: `${x}` })) });
return useCallback(
(systems: CommandRemoveSystems) => {
rf.deleteElements({ nodes: systems.map(x => ({ id: `${x}` })) });
const newSelection = ref.current.rf
.getNodes()
.filter(x => !systems.includes(parseInt(x.id)))
.filter(x => x.selected)
.map(x => x.id);
const newSelection = rf
.getNodes()
.filter(x => !systems.includes(parseInt(x.id)))
.filter(x => x.selected)
.map(x => x.id);
ref.current.onSelectionChange({
systems: newSelection,
connections: [],
});
}, []);
ref.current.onSelectionChange({
systems: newSelection,
connections: [],
});
},
[rf],
);
};

View File

@@ -42,7 +42,7 @@ export const useMapUpdateSystems = () => {
return newSystem;
});
update({ systems: out }, true);
update({ systems: out });
},
[rf, update],
);

View File

@@ -1,3 +1,2 @@
export * from './useMapHandlers';
export * from './useUpdateNodes';
export * from './useNodesEdgesState';

View File

@@ -1,44 +0,0 @@
import { useEffect, useState } from 'react';
import { BackgroundVariant } from 'reactflow';
export function useBackgroundVars(themeName?: string) {
const [variant, setVariant] = useState<BackgroundVariant>(BackgroundVariant.Dots);
const [gap, setGap] = useState<number>(16);
const [size, setSize] = useState<number>(1);
const [color, setColor] = useState('#81818b');
useEffect(() => {
// match any element whose entire `class` attribute ends with "-theme"
let themeEl = document.querySelector('[class$="-theme"]');
// If none is found, fall back to the <html> element
if (!themeEl) {
themeEl = document.documentElement;
}
const style = getComputedStyle(themeEl as HTMLElement);
const rawVariant = style.getPropertyValue('--rf-bg-variant').replace(/['"]/g, '').trim().toLowerCase();
let finalVariant: BackgroundVariant = BackgroundVariant.Dots;
if (rawVariant === 'lines') {
finalVariant = BackgroundVariant.Lines;
} else if (rawVariant === 'cross') {
finalVariant = BackgroundVariant.Cross;
}
const cssVarGap = style.getPropertyValue('--rf-bg-gap');
const cssVarSize = style.getPropertyValue('--rf-bg-size');
const cssColor = style.getPropertyValue('--rf-bg-pattern-color');
const gapNum = parseInt(cssVarGap, 10) || 16;
const sizeNum = parseInt(cssVarSize, 10) || 1;
setVariant(finalVariant);
setGap(gapNum);
setSize(sizeNum);
setColor(cssColor);
}, [themeName]);
return { variant, gap, size, color };
}

View File

@@ -19,6 +19,8 @@ import {
MapHandlers,
} from '@/hooks/Mapper/types/mapHandlers.ts';
import { useMapEventListener } from '@/hooks/Mapper/events';
import {
useCommandsCharacters,
useCommandsConnections,
@@ -58,19 +60,16 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
mapInit(data as CommandInit);
break;
case Commands.addSystems:
setTimeout(() => mapAddSystems(data as CommandAddSystems), 100);
break;
case Commands.updateSystems:
mapUpdateSystems(data as CommandUpdateSystems);
break;
case Commands.removeSystems:
setTimeout(() => removeSystems(data as CommandRemoveSystems), 100);
break;
case Commands.addConnections:
setTimeout(() => addConnections(data as CommandAddConnections), 100);
break;
case Commands.removeConnections:
setTimeout(() => removeConnections(data as CommandRemoveConnections), 100);
removeConnections(data as CommandRemoveConnections);
break;
case Commands.charactersUpdated:
charactersUpdated(data as CommandCharactersUpdated);
@@ -112,17 +111,13 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
connections: [],
});
selectSystem(systemId as CommandSelectSystem);
}, 500);
}, 100);
break;
case Commands.routes:
// do nothing here
break;
case Commands.signaturesUpdated:
// do nothing here
break;
case Commands.linkSignatureToSystem:
// do nothing here
break;
@@ -136,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;
}
});
};

View File

@@ -1,36 +0,0 @@
import { useState, useCallback, type Dispatch, type SetStateAction } from 'react';
import { applyNodeChanges, applyEdgeChanges } from '../utils/changes';
import { OnNodesChange, Edge, OnEdgesChange, Node } from 'reactflow';
/**
* Hook for managing the state of nodes - should only be used for prototyping / simple use cases.
*
* @public
* @param initialNodes
* @returns an array [nodes, setNodes, onNodesChange]
*/
export function useNodesState<NodeType extends Node>(
initialNodes: NodeType[],
): [NodeType[], Dispatch<SetStateAction<NodeType[]>>, OnNodesChange] {
const [nodes, setNodes] = useState(initialNodes);
const onNodesChange: OnNodesChange = useCallback(changes => setNodes(nds => applyNodeChanges(changes, nds)), []);
return [nodes, setNodes, onNodesChange];
}
/**
* Hook for managing the state of edges - should only be used for prototyping / simple use cases.
*
* @public
* @param initialEdges
* @returns an array [edges, setEdges, onEdgesChange]
*/
export function useEdgesState<EdgeType extends Edge = Edge>(
initialEdges: EdgeType[],
): [EdgeType[], Dispatch<SetStateAction<EdgeType[]>>, OnEdgesChange] {
const [edges, setEdges] = useState(initialEdges);
const onEdgesChange: OnEdgesChange = useCallback(changes => setEdges(eds => applyEdgeChanges(changes, eds)), []);
return [edges, setEdges, onEdgesChange];
}

View File

@@ -1,254 +0,0 @@
import { useMemo } from 'react';
import { MapSolarSystemType } from '../map.types';
import { NodeProps } from 'reactflow';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useMapGetOption } from '@/hooks/Mapper/mapRootProvider/hooks/api';
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider';
import { useDoubleClick } from '@/hooks/Mapper/hooks/useDoubleClick';
import { REGIONS_MAP, Spaces } from '@/hooks/Mapper/constants';
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace';
import { getSystemClassStyles, prepareUnsplashedChunks } from '@/hooks/Mapper/components/map/helpers';
import { sortWHClasses } from '@/hooks/Mapper/helpers';
import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager';
import { CharacterTypeRaw, OutCommand } from '@/hooks/Mapper/types';
import { LABELS_INFO, LABELS_ORDER } from '@/hooks/Mapper/components/map/constants';
function getActivityType(count: number) {
if (count <= 5) return 'activityNormal';
if (count <= 30) return 'activityWarn';
return 'activityDanger';
}
const SpaceToClass: Record<string, string> = {
[Spaces.Caldari]: 'Caldaria',
[Spaces.Matar]: 'Mataria',
[Spaces.Amarr]: 'Amarria',
[Spaces.Gallente]: 'Gallente',
};
function sortedLabels(labels: string[]) {
if (!labels) return [];
return LABELS_ORDER.filter(x => labels.includes(x)).map(x => LABELS_INFO[x]);
}
export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>) {
const { id, data, selected } = props;
const {
system_static_info,
system_signatures,
locked,
name,
tag,
status,
labels,
temporary_name,
linked_sig_eve_id: linkedSigEveId = '',
} = data;
const {
system_class,
security,
class_title,
solar_system_id,
statics,
effect_name,
region_name,
region_id,
is_shattered,
solar_system_name,
} = system_static_info;
const {
interfaceSettings,
data: { systemSignatures: mapSystemSignatures },
} = useMapRootState();
const { isShowUnsplashedSignatures } = interfaceSettings;
const isTempSystemNameEnabled = useMapGetOption('show_temp_system_name') === 'true';
const isShowLinkedSigId = useMapGetOption('show_linked_signature_id') === 'true';
const isShowLinkedSigIdTempName = useMapGetOption('show_linked_signature_id_temp_name') === 'true';
const {
data: {
characters,
presentCharacters,
wormholesData,
hubs,
kills,
userCharacters,
isConnecting,
hoverNodeId,
visibleNodes,
showKSpaceBG,
isThickConnections,
},
outCommand,
} = useMapState();
const visible = useMemo(() => visibleNodes.has(id), [id, visibleNodes]);
const systemSignatures = useMemo(
() => mapSystemSignatures[solar_system_id] || system_signatures,
[system_signatures, solar_system_id, mapSystemSignatures],
);
const charactersInSystem = useMemo(() => {
return characters.filter(c => c.location?.solar_system_id === solar_system_id).filter(c => c.online);
// eslint-disable-next-line
}, [characters, presentCharacters, solar_system_id]);
const isWormhole = isWormholeSpace(system_class);
const classTitleColor = useMemo(
() => getSystemClassStyles({ systemClass: system_class, security }),
[security, system_class],
);
const sortedStatics = useMemo(() => sortWHClasses(wormholesData, statics), [wormholesData, statics]);
const linkedSigPrefix = useMemo(() => (linkedSigEveId ? linkedSigEveId.split('-')[0] : null), [linkedSigEveId]);
const labelsManager = useMemo(() => new LabelsManager(labels ?? ''), [labels]);
const labelsInfo = useMemo(() => sortedLabels(labelsManager.list), [labelsManager]);
const labelCustom = useMemo(() => {
if (isShowLinkedSigId && linkedSigPrefix) {
return labelsManager.customLabel ? `${linkedSigPrefix}${labelsManager.customLabel}` : linkedSigPrefix;
}
return labelsManager.customLabel;
}, [linkedSigPrefix, isShowLinkedSigId, labelsManager]);
const killsCount = useMemo(() => kills[solar_system_id] ?? null, [kills, solar_system_id]);
const killsActivityType = killsCount ? getActivityType(killsCount) : null;
const hasUserCharacters = useMemo(() => {
return charactersInSystem.some(x => userCharacters.includes(x.eve_id));
}, [charactersInSystem, userCharacters]);
const dbClick = useDoubleClick(() => {
outCommand({
type: OutCommand.openSettings,
data: { system_id: solar_system_id.toString() },
});
});
const showHandlers = isConnecting || hoverNodeId === id;
const space = showKSpaceBG ? REGIONS_MAP[region_id] : '';
const regionClass = showKSpaceBG ? SpaceToClass[space] : null;
const temporaryName = useMemo(() => {
if (!isTempSystemNameEnabled) {
return '';
}
if (isShowLinkedSigIdTempName && linkedSigPrefix) {
return temporary_name ? `${linkedSigPrefix}${temporary_name}` : `${linkedSigPrefix}${solar_system_name}`;
}
return temporary_name;
}, [isShowLinkedSigIdTempName, isTempSystemNameEnabled, linkedSigPrefix, solar_system_name, temporary_name]);
const systemName = useMemo(() => {
if (isTempSystemNameEnabled && temporaryName) {
return temporaryName;
}
return solar_system_name;
}, [isTempSystemNameEnabled, solar_system_name, temporaryName]);
const customName = (isTempSystemNameEnabled && temporaryName && name) || (solar_system_name !== name && name) || null;
const [unsplashedLeft, unsplashedRight] = useMemo(() => {
if (!isShowUnsplashedSignatures) {
return [[], []];
}
return prepareUnsplashedChunks(
systemSignatures
.filter(s => s.group === 'Wormhole' && !s.linked_system)
.map(s => ({
eve_id: s.eve_id,
type: s.type,
custom_info: s.custom_info,
})),
);
}, [isShowUnsplashedSignatures, systemSignatures]);
const nodeVars = {
id,
selected,
visible,
isWormhole,
classTitleColor,
killsCount,
killsActivityType,
hasUserCharacters,
showHandlers,
regionClass,
systemName,
customName,
labelCustom,
isShattered: is_shattered,
tag,
status,
labelsInfo,
dbClick,
sortedStatics,
effectName: effect_name,
regionName: region_name,
solarSystemId: solar_system_id,
solarSystemName: solar_system_name,
locked,
hubs,
name: name,
isConnecting,
hoverNodeId,
charactersInSystem,
unsplashedLeft,
unsplashedRight,
isThickConnections,
classTitle: class_title,
temporaryName: temporary_name,
};
return nodeVars;
}
export interface SolarSystemNodeVars {
id: string;
selected: boolean;
visible: boolean;
isWormhole: boolean;
classTitleColor: string | null;
killsCount: number | null;
killsActivityType: string | null;
hasUserCharacters: boolean;
showHandlers: boolean;
regionClass: string | null;
systemName: string;
customName?: string | null;
labelCustom: string | null;
isShattered: boolean;
tag?: string | null;
status?: number;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
labelsInfo: Array<any>;
dbClick: (event?: void) => void;
sortedStatics: Array<string | number>;
effectName: string | null;
regionName: string | null;
solarSystemId: number;
solarSystemName: string | null;
locked: boolean;
hubs: string[] | number[];
name: string | null;
isConnecting: boolean;
hoverNodeId: string | null;
charactersInSystem: Array<CharacterTypeRaw>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
unsplashedLeft: Array<any>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
unsplashedRight: Array<any>;
isThickConnections: boolean;
classTitle: string | null;
temporaryName?: string | null;
}

View File

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

View File

@@ -1,31 +0,0 @@
@import './eve-common-variables';
@import './eve-common';
.default-theme {
--rf-bg-color: #0C0A09;
--rf-soft-bg-color: #171717;
--rf-node-bg-color: #202020;
--rf-node-soft-bg-color: #202020;
--rf-text-color: #ffffff;
--rf-tag-color: #38BDF8;
--rf-region-name: #D6D3D1;
--rf-custom-name: #93C5FD;
--rf-bg-variant: "dots";
--rf-bg-gap: 15;
--rf-bg-size: 1;
--rf-bg-pattern-color: #81818a;
--pastel-blue: #5a7d9a;
--pastel-pink: #d291bc;
--pastel-green: #88b04b;
--pastel-yellow: #ffdd59;
--dark-bg: #2d2d2d;
--text-color: #ffffff;
--tooltip-bg: #202020;
--window-corner: #72716f;
}

View File

@@ -1,121 +1,78 @@
$eve-link-color-default: #333;
$eve-link-color-top-mass-0: #333;
$eve-link-color-top-mass-1: #5a4520;
$eve-link-color-top-mass-2: #672c2c;
$eve-link-color-middle-mass-0: #333;
$eve-link-color-middle-mass-1: #333;
$eve-link-color-middle-mass-2: #333;
$eve-link-color-middle-time-0: #5c5c5c;
$eve-link-color-middle-time-1: #ff00cd;
$eve-link-color-middle-time-1-border: #99f3ff;
$friendlyBase: #3bbd39;
$friendlyAlpha: #3bbd3952;
$friendlyDark20: darken($friendlyBase, 20%);
$friendlyDark30: darken($friendlyBase, 30%);
$friendlyDark5: darken($friendlyBase, 5%);
$eve-link-color-top-mass-1-time-1: #796300;
$eve-link-color-top-mass-2-time-1: #8c1717;
$eve-link-color-temp: orange;
$lookingForBase: #43c2fd;
$lookingForAlpha: rgba(67, 176, 253, 0.48);
$lookingForDark15: darken($lookingForBase, 15%);
$eve-effect-pulsar: #40aef5;
$eve-effect-magnetar: #f058f8;
$eve-effect-wolfRayet: #ef7843;
$eve-effect-blackHole: #1b1b1b;
$eve-effect-cataclysmicVariable: #ffea90;
$eve-effect-redGiant: #fd3c3c;
$eve-effect-dazhLiminalityLocus: #ff6464;
$eve-effect-imperialStellarObservatory: #6991ce;
$eve-effect-stateStellarObservatory: #6991ce;
$eve-effect-republicStellarObservatory: #6991ce;
$eve-effect-federalStellarObservatory: #6991ce;
$homeBase: rgb(197, 253, 67);
$homeAlpha: rgba(197, 253, 67, 0.32);
$homeDark30: darken($homeBase, 30%);
$eve-wh-type-color-high: #5dffd2;
$eve-wh-type-color-low: #f79400;
$eve-wh-type-color-null: #fc3c3c;
$eve-wh-type-color-c1: #69bfce;
$eve-wh-type-color-c2: #6991ce;
$eve-wh-type-color-c3: #a8cb70;
$eve-wh-type-color-c4: #e39c68;
$eve-wh-type-color-c5: #de8686;
$eve-wh-type-color-c6: #e76363;
$eve-wh-type-color-c13: #988cb5;
$eve-wh-type-color-drifter: #ff44f6;
$eve-wh-type-color-thera: #ffffff;
$eve-wh-type-color-zarzakh: #212121;
$eve-security-color-10: #2c74df;
$eve-security-color-09: #3998e8;
$eve-security-color-08: #4dcbf5;
$eve-security-color-07: #60d8a2;
$eve-security-color-06: #71e454;
$eve-security-color-05: #f2fc81;
$eve-security-color-04: #d96c07;
$eve-security-color-03: #cb440f;
$eve-security-color-02: #b91117;
$eve-security-color-01: #732020;
$eve-security-color-00: #8b3263;
$eve-security-color-m-01: #8b3263;
$eve-security-color-m-02: #8b3263;
$eve-security-color-m-03: #8b3263;
$eve-security-color-m-04: #8b3263;
$eve-security-color-m-05: #8b3263;
$eve-security-color-m-06: #8b3263;
$eve-security-color-m-07: #8b3263;
$eve-security-color-m-08: #8b3263;
$eve-security-color-m-09: #8b3263;
$eve-security-color-m-10: #8b3263;
:root {
--pastel-blue: #5a7d9a;
--pastel-pink: #d291bc;
--pastel-green: #88b04b;
--pastel-yellow: #ffdd59;
--dark-bg: #2d2d2d;
--text-color: #ffffff;
--tooltip-bg: #202020;
$eve-solar-system-status-unknown: transparent;
$eve-solar-system-status-friendly: #3bbd3952;
$eve-solar-system-status-warning: #906518a6;
$eve-solar-system-status-target: #b439ff6b;
$eve-solar-system-status-dangerous: #d54040;
$eve-solar-system-status-lookingFor: rgba(67, 176, 253, 0.48);
$eve-solar-system-status-home: rgb(197, 253, 67);
--pastel-blue-darken10: #4f6b86;
--pastel-blue-lighten10: #6da3af;
--pastel-pink-darken10: #bb7ca9;
--pastel-pink-lighten10: #e0a6cb;
--pastel-green-darken10: #79a244;
--pastel-green-lighten10: #99cf52;
--pastel-yellow-darken10: #e6c44f;
--pastel-yellow-lighten10: #ffe874;
--eve-link-color-default: #333;
--eve-link-color-top-mass-0: #333;
--eve-link-color-top-mass-1: #5a4520;
--eve-link-color-top-mass-2: #672c2c;
--eve-link-color-middle-mass-0: #333;
--eve-link-color-middle-mass-1: #333;
--eve-link-color-middle-mass-2: #333;
--eve-link-color-middle-time-0: #5c5c5c;
--eve-link-color-middle-time-1: #ff00cd;
--eve-link-color-middle-time-1-border: #99f3ff;
--eve-link-color-top-mass-1-time-1: #796300;
--eve-link-color-top-mass-2-time-1: #8c1717;
--eve-link-color-temp: orange;
--eve-effect-pulsar: #40aef5;
--eve-effect-magnetar: #f058f8;
--eve-effect-wolfRayet: #ef7843;
--eve-effect-blackHole: #1b1b1b;
--eve-effect-cataclysmicVariable: #ffea90;
--eve-effect-redGiant: #fd3c3c;
--eve-effect-dazhLiminalityLocus: #ff6464;
--eve-effect-imperialStellarObservatory: #6991ce;
--eve-effect-stateStellarObservatory: #6991ce;
--eve-effect-republicStellarObservatory: #6991ce;
--eve-effect-federalStellarObservatory: #6991ce;
--eve-wh-type-color-high: #5dffd2;
--eve-wh-type-color-low: #f79400;
--eve-wh-type-color-null: #fc3c3c;
--eve-wh-type-color-c1: #69bfce;
--eve-wh-type-color-c2: #6991ce;
--eve-wh-type-color-c3: #a8cb70;
--eve-wh-type-color-c4: #e39c68;
--eve-wh-type-color-c5: #de8686;
--eve-wh-type-color-c6: #e76363;
--eve-wh-type-color-c13: #988cb5;
--eve-wh-type-color-drifter: #ff44f6;
--eve-wh-type-color-thera: #ffffff;
--eve-wh-type-color-zarzakh: #212121;
--eve-security-color-10: #2c74df;
--eve-security-color-09: #3998e8;
--eve-security-color-08: #4dcbf5;
--eve-security-color-07: #60d8a2;
--eve-security-color-06: #71e454;
--eve-security-color-05: #f2fc81;
--eve-security-color-04: #d96c07;
--eve-security-color-03: #cb440f;
--eve-security-color-02: #b91117;
--eve-security-color-01: #732020;
--eve-security-color-00: #8b3263;
--eve-security-color-m-01: #8b3263;
--eve-security-color-m-02: #8b3263;
--eve-security-color-m-03: #8b3263;
--eve-security-color-m-04: #8b3263;
--eve-security-color-m-05: #8b3263;
--eve-security-color-m-06: #8b3263;
--eve-security-color-m-07: #8b3263;
--eve-security-color-m-08: #8b3263;
--eve-security-color-m-09: #8b3263;
--eve-security-color-m-10: #8b3263;
--eve-solar-system-status-unknown: transparent;
--eve-solar-system-status-color-unknown: transparent;
--eve-solar-system-status-home: #{$homeAlpha};
--eve-solar-system-status-color-home: #{$homeBase};
--eve-solar-system-status-color-home-dark30: #{$homeDark30};
--eve-solar-system-status-friendly: #{$friendlyAlpha};
--eve-solar-system-status-color-friendly: #{$friendlyBase};
--eve-solar-system-status-friendly-dark30: #{$friendlyDark30};
--eve-solar-system-status-color-friendly-dark20: #{$friendlyDark20};
--eve-solar-system-status-color-friendly-dark5: #{$friendlyDark5};
--eve-solar-system-status-lookingFor: #{$lookingForAlpha};
--eve-solar-system-status-color-lookingFor: #{$lookingForBase};
--eve-solar-system-status-color-lookingFor-dark15: #{$lookingForDark15};
--eve-solar-system-status-warning: #906518a6;
--eve-solar-system-status-color-warning: #ffb93b;
--eve-solar-system-status-target: #b439ff6b;
--eve-solar-system-status-color-target: #b439ff;
--eve-solar-system-status-dangerous: #d54040;
--eve-solar-system-status-color-dangerous: #d54040;
--conn-time-eol: #7452c3e3;
--conn-frigate: #325d88;
--conn-save: rgba(155, 102, 45, 0.85);
--selected-item-bg: rgba(98, 98, 98, 0.33);
}
$eve-solar-system-status-color-unknown: transparent;
$eve-solar-system-status-color-friendly: #3bbd39;
$eve-solar-system-status-color-warning: #ffb93b;
$eve-solar-system-status-color-target: #b439ff;
$eve-solar-system-status-color-dangerous: #d54040;
$eve-solar-system-status-color-lookingFor: #43c2fd;
$eve-solar-system-status-color-home: rgb(197, 253, 67);

View File

@@ -1,504 +1,535 @@
@import './eve-common-variables';
@import "eve-common-variables";
.eve-wh-effect-color-pulsar {
fill: var(--eve-effect-pulsar);
background-color: var(--eve-effect-pulsar);
fill: $eve-effect-pulsar;
background-color: $eve-effect-pulsar;
}
.eve-wh-effect-color-magnetar {
fill: var(--eve-effect-magnetar);
background-color: var(--eve-effect-magnetar);
fill: $eve-effect-magnetar;
background-color: $eve-effect-magnetar;
}
.eve-wh-effect-color-wolfRayet {
fill: var(--eve-effect-wolfRayet);
background-color: var(--eve-effect-wolfRayet);
fill: $eve-effect-wolfRayet;
background-color: $eve-effect-wolfRayet;
}
.eve-wh-effect-color-blackHole {
fill: var(--eve-effect-blackHole);
background-color: var(--eve-effect-blackHole);
box-shadow: 0 0 8px rgba(255, 255, 255, 0.33);
fill: $eve-effect-blackHole;
background-color: $eve-effect-blackHole;
box-shadow: 0 0 8px rgba(255 255 255 / 33);
}
.eve-wh-effect-color-cataclysmicVariable {
fill: var(--eve-effect-cataclysmicVariable);
background-color: var(--eve-effect-cataclysmicVariable);
fill: $eve-effect-cataclysmicVariable;
background-color: $eve-effect-cataclysmicVariable;
}
.eve-wh-effect-color-redGiant {
fill: var(--eve-effect-redGiant);
background-color: var(--eve-effect-redGiant);
fill: $eve-effect-redGiant;
background-color: $eve-effect-redGiant;
}
.text-eve-wh-effect-color-pulsar {
color: var(--eve-effect-pulsar);
color: $eve-effect-pulsar;
}
.text-eve-wh-effect-color-magnetar {
color: var(--eve-effect-magnetar);
color: $eve-effect-magnetar;
}
.text-eve-wh-effect-color-wolfRayet {
color: var(--eve-effect-wolfRayet);
color: $eve-effect-wolfRayet;
}
.text-eve-wh-effect-color-blackHole {
color: #fff;
}
.text-eve-wh-effect-color-cataclysmicVariable {
color: var(--eve-effect-cataclysmicVariable);
color: $eve-effect-cataclysmicVariable;
}
.text-eve-wh-effect-color-redGiant {
color: var(--eve-effect-redGiant);
color: $eve-effect-redGiant;
}
.text-eve-wh-effect-color-dazhLiminalityLocus {
color: var(--eve-effect-dazhLiminalityLocus);
color: $eve-effect-dazhLiminalityLocus;
}
.text-eve-wh-effect-color-imperialStellarObservatory {
color: var(--eve-effect-imperialStellarObservatory);
color: $eve-effect-imperialStellarObservatory;
}
.text-eve-wh-effect-color-stateStellarObservatory {
color: var(--eve-effect-stateStellarObservatory);
color: $eve-effect-stateStellarObservatory;
}
.text-eve-wh-effect-color-republicStellarObservatory {
color: var(--eve-effect-republicStellarObservatory);
color: $eve-effect-republicStellarObservatory;
}
.text-eve-wh-effect-color-federalStellarObservatory {
color: var(--eve-effect-federalStellarObservatory);
color: $eve-effect-federalStellarObservatory;
}
/* Security color classes */
.eve-security-color-10 {
color: var(--eve-security-color-10) !important;
fill: var(--eve-security-color-10);
color: $eve-security-color-10 !important;
fill: $eve-security-color-10;
}
.eve-security-color-09 {
color: var(--eve-security-color-09) !important;
fill: var(--eve-security-color-09);
color: $eve-security-color-09 !important;
fill: $eve-security-color-09;
}
.eve-security-color-08 {
color: var(--eve-security-color-08) !important;
fill: var(--eve-security-color-08);
color: $eve-security-color-08 !important;
fill: $eve-security-color-08;
}
.eve-security-color-07 {
color: var(--eve-security-color-07) !important;
fill: var(--eve-security-color-07);
color: $eve-security-color-07 !important;
fill: $eve-security-color-07;
}
.eve-security-color-06 {
color: var(--eve-security-color-06) !important;
fill: var(--eve-security-color-06);
color: $eve-security-color-06 !important;
fill: $eve-security-color-06;
}
.eve-security-color-05 {
color: var(--eve-security-color-05) !important;
fill: var(--eve-security-color-05);
color: $eve-security-color-05 !important;
fill: $eve-security-color-05;
}
.eve-security-color-04 {
color: var(--eve-security-color-04) !important;
fill: var(--eve-security-color-04);
color: $eve-security-color-04 !important;
fill: $eve-security-color-04;
}
.eve-security-color-03 {
color: var(--eve-security-color-03) !important;
fill: var(--eve-security-color-03);
color: $eve-security-color-03 !important;
fill: $eve-security-color-03;
}
.eve-security-color-02 {
color: var(--eve-security-color-02) !important;
fill: var(--eve-security-color-02);
color: $eve-security-color-02 !important;
fill: $eve-security-color-02;
}
.eve-security-color-01 {
color: var(--eve-security-color-01) !important;
fill: var(--eve-security-color-01);
color: $eve-security-color-01 !important;
fill: $eve-security-color-01;
}
.eve-security-color-00 {
color: var(--eve-security-color-00) !important;
fill: var(--eve-security-color-00);
color: $eve-security-color-00 !important;
fill: $eve-security-color-00;
}
.eve-security-color-m-01 {
color: var(--eve-security-color-m-01) !important;
fill: var(--eve-security-color-m-01);
color: $eve-security-color-m-01 !important;
fill: $eve-security-color-m-01;
}
.eve-security-color-m-02 {
color: var(--eve-security-color-m-02) !important;
fill: var(--eve-security-color-m-02);
color: $eve-security-color-m-02 !important;
fill: $eve-security-color-m-02;
}
.eve-security-color-m-03 {
color: var(--eve-security-color-m-03) !important;
fill: var(--eve-security-color-m-03);
color: $eve-security-color-m-03 !important;
fill: $eve-security-color-m-03;
}
.eve-security-color-m-04 {
color: var(--eve-security-color-m-04) !important;
fill: var(--eve-security-color-m-04);
color: $eve-security-color-m-04 !important;
fill: $eve-security-color-m-04;
}
.eve-security-color-m-05 {
color: var(--eve-security-color-m-05) !important;
fill: var(--eve-security-color-m-05);
color: $eve-security-color-m-05 !important;
fill: $eve-security-color-m-05;
}
.eve-security-color-m-06 {
color: var(--eve-security-color-m-06) !important;
fill: var(--eve-security-color-m-06);
color: $eve-security-color-m-06 !important;
fill: $eve-security-color-m-06;
}
.eve-security-color-m-07 {
color: var(--eve-security-color-m-07) !important;
fill: var(--eve-security-color-m-07);
color: $eve-security-color-m-07 !important;
fill: $eve-security-color-m-07;
}
.eve-security-color-m-08 {
color: var(--eve-security-color-m-08) !important;
fill: var(--eve-security-color-m-08);
color: $eve-security-color-m-08 !important;
fill: $eve-security-color-m-08;
}
.eve-security-color-m-09 {
color: var(--eve-security-color-m-09) !important;
fill: var(--eve-security-color-m-09);
color: $eve-security-color-m-09 !important;
fill: $eve-security-color-m-09;
}
.eve-security-color-m-10 {
color: var(--eve-security-color-m-10) !important;
fill: var(--eve-security-color-m-10);
color: $eve-security-color-m-10 !important;
fill: $eve-security-color-m-10;
}
/* Security backgrounds */
.eve-security-background-10 {
background-color: var(--eve-security-color-10);
fill: var(--eve-security-color-10);
background-color: $eve-security-color-10;
fill: $eve-security-color-10;
}
.eve-security-background-09 {
background-color: var(--eve-security-color-09);
fill: var(--eve-security-color-09);
background-color: $eve-security-color-09;
fill: $eve-security-color-09;
}
.eve-security-background-08 {
background-color: var(--eve-security-color-08);
fill: var(--eve-security-color-08);
background-color: $eve-security-color-08;
fill: $eve-security-color-08;
}
.eve-security-background-07 {
background-color: var(--eve-security-color-07);
fill: var(--eve-security-color-07);
background-color: $eve-security-color-07;
fill: $eve-security-color-07;
}
.eve-security-background-06 {
background-color: var(--eve-security-color-06);
fill: var(--eve-security-color-06);
background-color: $eve-security-color-06;
fill: $eve-security-color-06;
}
.eve-security-background-05 {
background-color: var(--eve-security-color-05);
fill: var(--eve-security-color-05);
background-color: $eve-security-color-05;
fill: $eve-security-color-05;
}
.eve-security-background-04 {
background-color: var(--eve-security-color-04);
fill: var(--eve-security-color-04);
background-color: $eve-security-color-04;
fill: $eve-security-color-04;
}
.eve-security-background-03 {
background-color: var(--eve-security-color-03);
fill: var(--eve-security-color-03);
background-color: $eve-security-color-03;
fill: $eve-security-color-03;
}
.eve-security-background-02 {
background-color: var(--eve-security-color-02);
fill: var(--eve-security-color-02);
background-color: $eve-security-color-02;
fill: $eve-security-color-02;
}
.eve-security-background-01 {
background-color: var(--eve-security-color-01);
fill: var(--eve-security-color-01);
background-color: $eve-security-color-01;
fill: $eve-security-color-01;
}
.eve-security-background-00 {
background-color: var(--eve-security-color-00);
fill: var(--eve-security-color-00);
background-color: $eve-security-color-00;
fill: $eve-security-color-00;
}
.eve-security-background-m-01 {
background-color: var(--eve-security-color-m-01);
fill: var(--eve-security-color-m-01);
background-color: $eve-security-color-m-01;
fill: $eve-security-color-m-01;
}
.eve-security-background-m-02 {
background-color: var(--eve-security-color-m-02);
fill: var(--eve-security-color-m-02);
background-color: $eve-security-color-m-02;
fill: $eve-security-color-m-02;
}
.eve-security-background-m-03 {
background-color: var(--eve-security-color-m-03);
fill: var(--eve-security-color-m-03);
background-color: $eve-security-color-m-03;
fill: $eve-security-color-m-03;
}
.eve-security-background-m-04 {
background-color: var(--eve-security-color-m-04);
fill: var(--eve-security-color-m-04);
background-color: $eve-security-color-m-04;
fill: $eve-security-color-m-04;
}
.eve-security-background-m-05 {
background-color: var(--eve-security-color-m-05);
fill: var(--eve-security-color-m-05);
background-color: $eve-security-color-m-05;
fill: $eve-security-color-m-05;
}
.eve-security-background-m-06 {
background-color: var(--eve-security-color-m-06);
fill: var(--eve-security-color-m-06);
background-color: $eve-security-color-m-06;
fill: $eve-security-color-m-06;
}
.eve-security-background-m-07 {
background-color: var(--eve-security-color-m-07);
fill: var(--eve-security-color-m-07);
background-color: $eve-security-color-m-07;
fill: $eve-security-color-m-07;
}
.eve-security-background-m-08 {
background-color: var(--eve-security-color-m-08);
fill: var(--eve-security-color-m-08);
background-color: $eve-security-color-m-08;
fill: $eve-security-color-m-08;
}
.eve-security-background-m-09 {
background-color: var(--eve-security-color-m-09);
fill: var(--eve-security-color-m-09);
background-color: $eve-security-color-m-09;
fill: $eve-security-color-m-09;
}
.eve-security-background-m-10 {
background-color: var(--eve-security-color-m-10);
fill: var(--eve-security-color-m-10);
background-color: $eve-security-color-m-10;
fill: $eve-security-color-m-10;
}
/* WH Type color classes */
.eve-wh-type-color-high {
color: var(--eve-wh-type-color-high) !important;
fill: var(--eve-wh-type-color-high);
color: $eve-wh-type-color-high;
fill: $eve-wh-type-color-high;
font-weight: bold !important;
}
.eve-wh-type-color-low {
color: var(--eve-wh-type-color-low) !important;
fill: var(--eve-wh-type-color-low);
color: $eve-wh-type-color-low;
fill: $eve-wh-type-color-low;
font-weight: bold !important;
}
.eve-wh-type-color-null {
color: var(--eve-wh-type-color-null) !important;
fill: var(--eve-wh-type-color-null);
color: $eve-wh-type-color-null;
fill: $eve-wh-type-color-null;
font-weight: bold !important;
}
.eve-wh-type-color-c1 {
color: var(--eve-wh-type-color-c1) !important;
fill: var(--eve-wh-type-color-c1);
color: $eve-wh-type-color-c1 !important;
fill: $eve-wh-type-color-c1;
font-weight: bold !important;
}
.eve-wh-type-color-c2 {
color: var(--eve-wh-type-color-c2) !important;
fill: var(--eve-wh-type-color-c2);
color: $eve-wh-type-color-c2 !important;
fill: $eve-wh-type-color-c2;
font-weight: bold !important;
}
.eve-wh-type-color-c3 {
color: var(--eve-wh-type-color-c3) !important;
fill: var(--eve-wh-type-color-c3);
color: $eve-wh-type-color-c3 !important;
fill: $eve-wh-type-color-c3;
font-weight: bold !important;
}
.eve-wh-type-color-c4 {
color: var(--eve-wh-type-color-c4) !important;
fill: var(--eve-wh-type-color-c4);
color: $eve-wh-type-color-c4 !important;
fill: $eve-wh-type-color-c4;
font-weight: bold !important;
}
.eve-wh-type-color-c5 {
color: var(--eve-wh-type-color-c5) !important;
fill: var(--eve-wh-type-color-c5);
color: $eve-wh-type-color-c5 !important;
fill: $eve-wh-type-color-c5;
font-weight: bold !important;
}
.eve-wh-type-color-c6 {
color: var(--eve-wh-type-color-c6) !important;
fill: var(--eve-wh-type-color-c6);
color: $eve-wh-type-color-c6 !important;
fill: $eve-wh-type-color-c6;
font-weight: bold !important;
}
.eve-wh-type-color-c13 {
color: var(--eve-wh-type-color-c13) !important;
fill: var(--eve-wh-type-color-c13);
color: $eve-wh-type-color-c13 !important;
fill: $eve-wh-type-color-c13;
}
.eve-wh-type-color-drifter {
color: var(--eve-wh-type-color-drifter) !important;
fill: var(--eve-wh-type-color-drifter);
color: $eve-wh-type-color-drifter !important;
fill: $eve-wh-type-color-drifter;
}
.eve-wh-type-color-thera {
color: var(--eve-wh-type-color-thera) !important;
fill: var(--eve-wh-type-color-thera);
color: $eve-wh-type-color-thera !important;
fill: $eve-wh-type-color-thera;
}
/* WH Type backgrounds */
.eve-wh-type-background-high {
background-color: var(--eve-wh-type-color-high);
}
.eve-wh-type-background-low {
background-color: var(--eve-wh-type-color-low);
}
.eve-wh-type-background-null {
background-color: var(--eve-wh-type-color-null);
}
.eve-wh-type-background-c1 {
background-color: var(--eve-wh-type-color-c1);
}
.eve-wh-type-background-c2 {
background-color: var(--eve-wh-type-color-c2);
}
.eve-wh-type-background-c3 {
background-color: var(--eve-wh-type-color-c3);
}
.eve-wh-type-background-c4 {
background-color: var(--eve-wh-type-color-c4);
}
.eve-wh-type-background-c5 {
background-color: var(--eve-wh-type-color-c5);
}
.eve-wh-type-background-c6 {
background-color: var(--eve-wh-type-color-c6);
}
.eve-wh-type-background-c13 {
background-color: var(--eve-wh-type-color-c13);
}
.eve-wh-type-background-drifter {
background-color: var(--eve-wh-type-color-drifter);
}
.eve-wh-type-background-thera {
background-color: var(--eve-wh-type-color-thera);
}
.eve-wh-type-background-zarzakh {
background-color: var(--eve-wh-type-color-zarzakh);
background-color: $eve-wh-type-color-high;
}
/* Kind color classes */
.eve-kind-color-high {
color: var(--eve-wh-type-color-high);
fill: var(--eve-wh-type-color-high);
.eve-wh-type-background-low {
background-color: $eve-wh-type-color-low;
}
.eve-wh-type-background-null {
background-color: $eve-wh-type-color-null;
}
.eve-wh-type-background-c1 {
background-color: $eve-wh-type-color-c1;
}
.eve-wh-type-background-c2 {
background-color: $eve-wh-type-color-c2;
}
.eve-wh-type-background-c3 {
background-color: $eve-wh-type-color-c3;
}
.eve-wh-type-background-c4 {
background-color: $eve-wh-type-color-c4;
}
.eve-wh-type-background-c5 {
background-color: $eve-wh-type-color-c5;
}
.eve-wh-type-background-c6 {
background-color: $eve-wh-type-color-c6;
}
.eve-wh-type-background-c13 {
background-color: $eve-wh-type-color-c13;
}
.eve-wh-type-background-drifter {
background-color: $eve-wh-type-color-drifter;
}
.eve-wh-type-background-thera {
background-color: $eve-wh-type-color-thera;
}
.eve-wh-type-background-zarzakh {
background-color: $eve-wh-type-color-zarzakh;
}
.eve-kind-color-high {
color: $eve-wh-type-color-high;
fill: $eve-wh-type-color-high;
}
.eve-kind-color-low {
color: var(--eve-wh-type-color-low);
fill: var(--eve-wh-type-color-low);
color: $eve-wh-type-color-low;
fill: $eve-wh-type-color-low;
font-weight: bold;
}
.eve-kind-color-null {
color: var(--eve-wh-type-color-null);
fill: var(--eve-wh-type-color-null);
color: $eve-wh-type-color-null;
fill: $eve-wh-type-color-null;
}
.eve-kind-color-wh {
color: var(--eve-wh-type-color-c6);
fill: var(--eve-wh-type-color-c6);
color: $eve-wh-type-color-c6;
fill: $eve-wh-type-color-c6;
}
.eve-kind-color-thera {
color: var(--eve-wh-type-color-thera);
fill: var(--eve-wh-type-color-thera);
color: $eve-wh-type-color-thera;
fill: $eve-wh-type-color-thera;
}
.eve-kind-color-abyss {
color: var(--eve-wh-type-color-c6);
fill: var(--eve-wh-type-color-c6);
color: $eve-wh-type-color-c6;
fill: $eve-wh-type-color-c6;
}
.eve-kind-color-penalty {
color: var(--eve-wh-type-color-c6);
fill: var(--eve-wh-type-color-c6);
color: $eve-wh-type-color-c6;
fill: $eve-wh-type-color-c6;
}
.eve-kind-color-pochven {
color: var(--eve-wh-type-color-c6);
fill: var(--eve-wh-type-color-c6);
color: $eve-wh-type-color-c6;
fill: $eve-wh-type-color-c6;
}
.eve-kind-color-zarzakh {
color: var(--eve-wh-type-color-zarzakh);
fill: var(--eve-wh-type-color-zarzakh);
color: $eve-wh-type-color-zarzakh;
fill: $eve-wh-type-color-zarzakh;
}
/* Kind backgrounds */
.eve-kind-background-high {
background-color: var(--eve-wh-type-color-high);
background-color: $eve-wh-type-color-high;
}
.eve-kind-background-low {
background-color: var(--eve-wh-type-color-low);
background-color: $eve-wh-type-color-low;
}
.eve-kind-background-null {
background-color: var(--eve-wh-type-color-null);
background-color: $eve-wh-type-color-null;
}
.eve-kind-background-wh {
background-color: var(--eve-wh-type-color-c6);
background-color: $eve-wh-type-color-c6;
}
.eve-kind-background-thera {
background-color: var(--eve-wh-type-color-thera);
background-color: $eve-wh-type-color-thera;
}
.eve-kind-background-abyss {
background-color: var(--eve-wh-type-color-c6);
background-color: $eve-wh-type-color-c6;
}
.eve-kind-background-penalty {
background-color: var(--eve-wh-type-color-c6);
background-color: $eve-wh-type-color-c6;
}
.eve-kind-background-pochven {
background-color: var(--eve-wh-type-color-c6);
background-color: $eve-wh-type-color-c6;
}
.eve-kind-background-zarzakh {
background-color: var(--eve-wh-type-color-zarzakh);
background-color: $eve-wh-type-color-zarzakh;
}
/* System status color classes */
.eve-system-status-color-clear {
color: var(--eve-solar-system-status-color-unknown);
color: $eve-solar-system-status-color-unknown;
}
.eve-system-status-color-home {
color: var(--eve-solar-system-status-color-home);
color: $eve-solar-system-status-color-home;
}
.eve-system-status-color-friendly {
color: var(--eve-solar-system-status-color-friendly);
color: $eve-solar-system-status-color-friendly;
}
.eve-system-status-color-lookingFor {
color: var(--eve-solar-system-status-color-lookingFor);
color: $eve-solar-system-status-color-lookingFor;
}
.eve-system-status-color-warning {
color: var(--eve-solar-system-status-color-warning);
color: $eve-solar-system-status-color-warning;
}
.eve-system-status-color-target {
color: var(--eve-solar-system-status-color-target);
color: $eve-solar-system-status-color-target;
}
.eve-system-status-color-dangerous {
color: var(--eve-solar-system-status-color-dangerous);
}
.eve-system-status-clear {
background-color: var(--eve-solar-system-status-unknown);
}
.eve-system-status-home {
background-color: var(--eve-solar-system-status-home);
}
.eve-system-status-friendly {
background-color: var(--eve-solar-system-status-friendly);
}
.eve-system-status-lookingFor {
background-color: var(--eve-solar-system-status-lookingFor);
}
.eve-system-status-warning {
background-color: var(--eve-solar-system-status-warning);
}
.eve-system-status-target {
background-color: var(--eve-solar-system-status-target);
}
.eve-system-status-dangerous {
background-color: var(--eve-solar-system-status-dangerous);
}
.eve-system-status-clear {
background-color: var(--eve-solar-system-status-unknown);
color: var(--eve-solar-system-status-color-unknown);
}
.eve-system-status-home {
background-color: var(--eve-solar-system-status-home);
color: var(--eve-solar-system-status-color-home);
}
.eve-system-status-friendly {
background-color: var(--eve-solar-system-status-friendly);
color: var(--eve-solar-system-status-color-friendly);
}
.eve-system-status-lookingFor {
background-color: var(--eve-solar-system-status-lookingFor);
color: var(--eve-solar-system-status-color-lookingFor);
}
.eve-system-status-warning {
background-color: var(--eve-solar-system-status-warning);
color: var(--eve-solar-system-status-color-warning);
}
.eve-system-status-target {
background-color: var(--eve-solar-system-status-target);
color: var(--eve-solar-system-status-color-target);
}
.eve-system-status-dangerous {
background-color: var(--eve-solar-system-status-dangerous);
color: var(--eve-solar-system-status-color-dangerous);
color: $eve-solar-system-status-color-dangerous;
}
.wd-route-system-shape-triangle {
clip-path: polygon(50% 0, 0 100%, 100% 100%);
}
.wd-route-system-shape-circle {
border-radius: 40%;
}
/* Some additional background classes */
.wd-marker-bookmark-color-shattered {
background-color: #833ca4;
margin-top: 1px;
}
.wd-marker-bookmark-color-custom {
background-color: #282828;
border: 1px solid #4c4c4c;
@@ -541,49 +572,3 @@
.wd-marker-bookmark-color-danger {
background-color: #d10600;
}
.react-flow {
color: var(--text-color);
&__pane {
cursor: auto;
}
&__minimap {
background-color: rgba(66, 66, 66, 1);
opacity: 0.7;
border: 1px solid #2f2f2f;
border-radius: 4px;
overflow: hidden;
}
&__minimap-mask {
fill: rgba(28, 28, 28, 0.75);
}
&__controls {
filter: brightness(1.5);
}
&__minimap-node {
fill: #ffb03a;
}
}
.context-menu-active {
background-color: rgba(131, 131, 131, 0.33);
}
.p-dialog {
.p-dialog-header {
height: 40px;
padding: 1rem;
padding-right: 10px !important;
}
.p-dialog-title {
font-size: 1rem !important;
}
.p-dialog-header-icons {
align-self: initial !important;
}
}

View File

@@ -1,2 +0,0 @@
@import './default-theme.scss';
@import './pathfinder-theme.scss';

View File

@@ -0,0 +1,74 @@
$pastel-blue: #5a7d9a;
$pastel-pink: #d291bc;
$pastel-green: #88b04b;
$pastel-yellow: #ffdd59;
$dark-bg: #2d2d2d;
$text-color: #ffffff;
$tooltip-bg: #202020;
.react-flow {
// background-color: $dark-bg;
color: $text-color;
&__node {
//cursor: auto;
}
&__pane {
cursor: auto;
}
//&__edge {
// stroke: $pastel-pink;
// stroke-width: 2px;
//
// &.selected {
// stroke: $pastel-yellow;
// }
//}
&__handle {
//background-color: $pastel-green;
//box-shadow: 0 0 5px rgba($pastel-green, 0.5);
}
&__minimap {
background-color: rgba(66, 66, 66, 1);
opacity: 0.7;
//backdrop-filter: blur(5px);
border: 1px solid #2f2f2f;
border-radius: 4px;
overflow: hidden;
}
&__minimap-mask {
fill: rgba(28, 28, 28, 0.75);
}
&__controls {
filter: brightness(1.5);
}
&__minimap-node {
fill: #ffb03a;
}
}
.context-menu-active {
background-color: rgba(131, 131, 131, 0.33);
}
.p-dialog {
.p-dialog-header {
height: 40px;
padding: 1rem;
padding-right: 10px !important;
}
.p-dialog-title {
font-size: 1rem !important;
}
.p-dialog-header-icons {
align-self: initial !important;
}
}

View File

@@ -0,0 +1,7 @@
$pastel-blue: #5a7d9a;
$pastel-pink: #d291bc;
$pastel-green: #88b04b;
$pastel-yellow: #ffdd59;
$dark-bg: #2d2d2d;
$text-color: #ffffff;
$tooltip-bg: #202020;

View File

@@ -1,51 +0,0 @@
@import './eve-common-variables';
@import './eve-common';
@import url('https://fonts.googleapis.com/css2?family=Oxygen:wght@300;400;700&display=swap');
.pathfinder-theme {
--rf-bg-color: #000000;
--rf-soft-bg-color: #282828;
--rf-node-bg-color: #202020;
--rf-node-soft-bg-color: #313335;
--rf-node-font-weight: bold;
--rf-text-color: #adadad;
--rf-region-name: var(--rf-text-color);
--rf-custom-name: var(--rf-text-color);
--tooltip-bg: #202020;
--rf-bg-variant: "lines";
--rf-bg-gap: 32;
--rf-bg-size: 1;
--rf-bg-pattern-color: #313131;
--eve-effect-pulsar: #428bca;
--eve-effect-magnetar: #e06fdf;
--eve-effect-wolfRayet: #e28a0d;
--eve-effect-blackHole: #000000;
--eve-effect-cataclysmicVariable: #ffffbb;
--eve-effect-redGiant: #d9534f;
--eve-wh-type-color-high: #5cb85c;
--eve-wh-type-color-low: #e28a0d;
--eve-wh-type-color-null: #d9534f;
--eve-wh-type-color-c1: #428bca;
--eve-wh-type-color-c2: #428bca;
--eve-wh-type-color-c3: #e28a0d;
--eve-wh-type-color-c4: #e28a0d;
--eve-wh-type-color-c5: #d9534f;
--eve-wh-type-color-c6: #d9534f;
--eve-wh-type-color-c13: #7986cb;
--eve-wh-type-color-drifter: #44aa82;
--rf-node-font-weight: bold;
--rf-node-line-height: normal;
--rf-node-font-family: 'Oxygen', sans-serif;
--rf-node-text-color: var(--pf-text-color);
--rf-tag-color: #fbbf24;
--rf-has-user-characters: #5cb85c;
--window-corner: #72716f;
}

View File

@@ -1,174 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { EdgeChange, NodeChange, Node, Edge } from 'reactflow';
// This function applies changes to nodes or edges that are triggered by React Flow internally.
// When you drag a node for example, React Flow will send a position change update.
// This function then applies the changes and returns the updated elements.
function applyChanges(changes: any[], elements: any[]): any[] {
// we need this hack to handle the setNodes and setEdges function of the useReactFlow hook for controlled flows
if (changes.some(c => c.type === 'reset')) {
return changes.filter(c => c.type === 'reset').map(c => c.item);
}
const updatedElements: any[] = [];
// By storing a map of changes for each element, we can a quick lookup as we
// iterate over the elements array!
const changesMap = new Map<any, any[]>();
const addItemChanges: any[] = [];
for (const change of changes) {
if (change.type === 'add') {
addItemChanges.push(change);
continue;
} else if (change.type === 'remove' || change.type === 'replace') {
// For a 'remove' change we can safely ignore any other changes queued for
// the same element, it's going to be removed anyway!
changesMap.set(change.id, [change]);
} else {
const elementChanges = changesMap.get(change.id);
if (elementChanges) {
// If we have some changes queued already, we can do a mutable update of
// that array and save ourselves some copying.
elementChanges.push(change);
} else {
changesMap.set(change.id, [change]);
}
}
}
for (const element of elements) {
const changes = changesMap.get(element.id);
// When there are no changes for an element we can just push it unmodified,
// no need to copy it.
if (!changes) {
updatedElements.push(element);
continue;
}
// If we have a 'remove' change queued, it'll be the only change in the array
if (changes[0].type === 'remove') {
continue;
}
if (changes[0].type === 'replace') {
updatedElements.push({ ...changes[0].item });
continue;
}
// For other types of changes, we want to start with a shallow copy of the
// object so React knows this element has changed. Sequential changes will
/// each _mutate_ this object, so there's only ever one copy.
const updatedElement = { ...element };
for (const change of changes) {
applyChange(change, updatedElement);
}
updatedElements.push(updatedElement);
}
// we need to wait for all changes to be applied before adding new items
// to be able to add them at the correct index
if (addItemChanges.length) {
addItemChanges.forEach(change => {
if (change.index !== undefined) {
updatedElements.splice(change.index, 0, { ...change.item });
} else {
updatedElements.push({ ...change.item });
}
});
}
return updatedElements;
}
// Applies a single change to an element. This is a *mutable* update.
function applyChange(change: any, element: any): any {
switch (change.type) {
case 'select': {
element.selected = change.selected;
break;
}
case 'position': {
if (typeof change.position !== 'undefined') {
element.position = change.position;
}
if (typeof change.dragging !== 'undefined') {
element.dragging = change.dragging;
}
break;
}
case 'dimensions': {
if (typeof change.dimensions !== 'undefined') {
element.measured ??= {};
element.measured.width = change.dimensions.width;
element.measured.height = change.dimensions.height;
if (change.setAttributes) {
element.width = change.dimensions.width;
element.height = change.dimensions.height;
}
}
if (typeof change.resizing === 'boolean') {
element.resizing = change.resizing;
}
break;
}
}
}
/**
* Drop in function that applies node changes to an array of nodes.
* @public
* @remarks Various events on the <ReactFlow /> component can produce an {@link NodeChange} that describes how to update the edges of your flow in some way.
If you don't need any custom behaviour, this util can be used to take an array of these changes and apply them to your edges.
* @param changes - Array of changes to apply
* @param nodes - Array of nodes to apply the changes to
* @returns Array of updated nodes
* @example
* const onNodesChange = useCallback(
(changes) => {
setNodes((oldNodes) => applyNodeChanges(changes, oldNodes));
},
[setNodes],
);
return (
<ReactFLow nodes={nodes} edges={edges} onNodesChange={onNodesChange} />
);
*/
export function applyNodeChanges<NodeType extends Node = Node>(changes: NodeChange[], nodes: NodeType[]): NodeType[] {
return applyChanges(changes, nodes) as NodeType[];
}
/**
* Drop in function that applies edge changes to an array of edges.
* @public
* @remarks Various events on the <ReactFlow /> component can produce an {@link EdgeChange} that describes how to update the edges of your flow in some way.
If you don't need any custom behaviour, this util can be used to take an array of these changes and apply them to your edges.
* @param changes - Array of changes to apply
* @param edges - Array of edge to apply the changes to
* @returns Array of updated edges
* @example
* const onEdgesChange = useCallback(
(changes) => {
setEdges((oldEdges) => applyEdgeChanges(changes, oldEdges));
},
[setEdges],
);
return (
<ReactFlow nodes={nodes} edges={edges} onEdgesChange={onEdgesChange} />
);
*/
export function applyEdgeChanges<EdgeType extends Edge = Edge>(changes: EdgeChange[], edges: EdgeType[]): EdgeType[] {
return applyChanges(changes, edges) as EdgeType[];
}

View File

@@ -1,25 +1,77 @@
import 'react-grid-layout/css/styles.css';
import 'react-resizable/css/styles.css';
import { useMemo } from 'react';
import { WindowManager } from '@/hooks/Mapper/components/ui-kit/WindowManager';
import { DEFAULT_WIDGETS } from '@/hooks/Mapper/components/mapInterface/constants.tsx';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { WidgetGridItem, WidgetsGrid } from '@/hooks/Mapper/components/mapInterface/components';
import {
LocalCharacters,
RoutesWidget,
SystemInfo,
SystemSignatures,
} from '@/hooks/Mapper/components/mapInterface/widgets';
import { useState } from 'react';
import { SESSION_KEY } from '@/hooks/Mapper/constants.ts';
// import { debounce } from 'lodash/debounce';
const DEFAULT_WINDOWS = [
{
name: 'info',
rightOffset: 5,
width: 5,
height: 4,
item: () => <SystemInfo />,
},
{
name: 'local',
rightOffset: 5,
topOffset: 4,
width: 5,
height: 4,
item: () => <LocalCharacters />,
},
{ name: 'signatures', width: 8, height: 4, topOffset: 8, rightOffset: 12, item: () => <SystemSignatures /> },
{
name: 'routes',
rightOffset: 0,
topOffset: 8,
width: 5,
height: 6,
item: () => <RoutesWidget />,
},
];
const saveWindowsToLS = (toSaveItems: WidgetGridItem[]) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const out = toSaveItems.map(({ item, ...rest }) => rest);
localStorage.setItem(SESSION_KEY.windows, JSON.stringify(out));
};
const restoreWindowsFromLS = (): WidgetGridItem[] => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const raw = localStorage.getItem(SESSION_KEY.windows);
if (!raw) {
return DEFAULT_WINDOWS;
}
// eslint-disable-next-line no-debugger
const out = (JSON.parse(raw) as Omit<WidgetGridItem, 'item'>[])
.filter(x => DEFAULT_WINDOWS.find(def => def.name === x.name))
.map(x => {
const windowItem = DEFAULT_WINDOWS.find(def => def.name === x.name)?.item;
return { ...x, item: windowItem! };
});
return out;
};
export const MapInterface = () => {
// const [items, setItems] = useState<WindowProps[]>(restoreWindowsFromLS);
const { windowsSettings, updateWidgetSettings } = useMapRootState();
const [items, setItems] = useState<WidgetGridItem[]>(restoreWindowsFromLS());
const items = useMemo(() => {
return windowsSettings.windows
.map(x => {
const content = DEFAULT_WIDGETS.find(y => y.id === x.id)?.content;
return {
...x,
content: content!,
};
})
.filter(x => windowsSettings.visible.some(j => x.id === j));
}, [windowsSettings]);
return <WindowManager windows={items} dragSelector=".react-grid-dragHandleExample" onChange={updateWidgetSettings} />;
return (
<WidgetsGrid
items={items}
onChange={x => {
saveWindowsToLS(x);
setItems(x);
}}
/>
);
};

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
import { useCallback, useEffect, useRef } from 'react';
import { useCallback, useRef } from 'react';
import { Dialog } from 'primereact/dialog';
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
@@ -6,23 +6,17 @@ import { SystemSignature } from '@/hooks/Mapper/types';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { CommandLinkSignatureToSystem } from '@/hooks/Mapper/types';
import { SystemSignaturesContent } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignaturesContent';
import { SHOW_DESCRIPTION_COLUMN_SETTING } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatures';
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 },
{ key: SignatureGroup.Wormhole, name: 'Wormhole', value: true },
{ key: SHOW_DESCRIPTION_COLUMN_SETTING, name: 'Show Description Column', value: true, isFilter: false },
];
const signatureSettings: Setting[] = [{ key: COSMIC_SIGNATURE, name: 'Show Cosmic Signatures', value: true }];
export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignatureDialogProps) => {
const { outCommand } = useMapRootState();
@@ -58,14 +52,13 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat
<Dialog
header="Select signature to link"
visible
draggable={true}
draggable={false}
style={{ width: '500px' }}
onHide={handleHide}
contentClassName="!p-0"
>
<SystemSignaturesContent
systemId={`${data.solar_system_source}`}
hideLinkedSignatures
settings={signatureSettings}
onSelect={handleSelect}
selectable={true}

View File

@@ -3,7 +3,6 @@ import { InputTextarea } from 'primereact/inputtextarea';
import { Dialog } from 'primereact/dialog';
import { getSystemById } from '@/hooks/Mapper/helpers';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useMapGetOption } from '@/hooks/Mapper/mapRootProvider/hooks/api';
import { useCallback, useEffect, useRef, useState } from 'react';
import { Button } from 'primereact/button';
import { OutCommand } from '@/hooks/Mapper/types';
@@ -23,21 +22,30 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
outCommand,
} = useMapRootState();
const isTempSystemNameEnabled = useMapGetOption('show_temp_system_name') === 'true';
const system = getSystemById(systems, systemId);
const [name, setName] = useState('');
const [label, setLabel] = useState('');
const [temporaryName, setTemporaryName] = useState('');
const [description, setDescription] = useState('');
const inputRef = useRef<HTMLInputElement>();
const ref = useRef({ name, description, temporaryName, label, outCommand, systemId, system });
ref.current = { name, description, label, temporaryName, outCommand, systemId, system };
useEffect(() => {
if (!system) {
return;
}
const labels = new LabelsManager(system.labels || '');
setName(system.name || '');
setLabel(labels.customLabel);
setDescription(system.description || '');
}, [system]);
const ref = useRef({ name, description, label, outCommand, systemId, system });
ref.current = { name, description, label, outCommand, systemId, system };
const handleSave = useCallback(() => {
const { name, description, label, temporaryName, outCommand, systemId, system } = ref.current;
const { name, description, label, outCommand, systemId, system } = ref.current;
const outLabel = new LabelsManager(system?.labels ?? '');
outLabel.updateCustomLabel(label);
@@ -50,14 +58,6 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
},
});
outCommand({
type: OutCommand.updateSystemTemporaryName,
data: {
system_id: systemId,
value: temporaryName,
},
});
outCommand({
type: OutCommand.updateSystemName,
data: {
@@ -93,21 +93,6 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
e.target.value = e.target.value.toUpperCase().replace(/[^A-Z0-9\-[\](){}]/g, '');
}, []);
// Attention: this effect should be call only on mount.
useEffect(() => {
const { system } = ref.current;
if (!system) {
return;
}
const labels = new LabelsManager(system.labels || '');
setName(system.name || '');
setLabel(labels.customLabel);
setTemporaryName(system.temporary_name || '');
setDescription(system.description || '');
}, []);
return (
<Dialog
header="System settings"
@@ -182,35 +167,6 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
</IconField>
</div>
{isTempSystemNameEnabled && (
<div className="flex flex-col gap-1">
<label htmlFor="username">Temporary Name</label>
<IconField>
{temporaryName !== '' && (
<WdImgButton
className="pi pi-trash text-red-400"
textSize={WdImageSize.large}
tooltip={{
content: 'Remove temporary name',
className: 'pi p-input-icon',
position: TooltipPosition.top,
}}
onClick={() => setTemporaryName('')}
/>
)}
<InputText
id="temporaryName"
aria-describedby="temporaryName"
autoComplete="off"
value={temporaryName}
maxLength={10}
onChange={e => setTemporaryName(e.target.value)}
/>
</IconField>
</div>
)}
<div className="flex flex-col gap-1">
<label htmlFor="username">Description</label>
<InputTextarea

View File

@@ -0,0 +1,37 @@
.GridLayoutWrapper {
width: 100%;
height: 100% !important;
}
.GridLayout {
width: 100%;
height: 100% !important;
pointer-events: none;
& > div {
pointer-events: initial;
}
:global {
.react-resizable-handle::after {
border-color: #696969 !important;
}
.react-grid-placeholder {
background-color: rgba(147, 147, 147, 0.3);
//filter: blur(5px);
border: 2px dashed #b6b6b6;
}
.react-grid-item {
transition-property: none !important;
}
.react-grid-item.cssTransforms {
transition-property: none !important;
}
}
}

View File

@@ -0,0 +1,196 @@
import React, { useEffect, useRef, useState } from 'react';
import classes from './WidgetsGrid.module.scss';
import { ItemCallback, Layouts, Responsive, WidthProvider } from 'react-grid-layout';
import clsx from 'clsx';
import usePageVisibility from '@/hooks/Mapper/hooks/usePageVisibility.ts';
const ResponsiveGridLayout = WidthProvider(Responsive);
const colSize = 50;
const initState = { breakpoints: 100, cols: 2 };
export type WidgetGridItem = {
rightOffset?: number;
leftOffset?: number;
topOffset?: number;
width: number;
height: number;
name: string;
item: () => React.ReactNode;
};
export interface WidgetsGridProps {
items: WidgetGridItem[];
onChange: (items: WidgetGridItem[]) => void;
}
export const WidgetsGrid = ({ items, onChange }: WidgetsGridProps) => {
const containerRef = useRef<HTMLDivElement>(null);
const [, setKey] = useState(0);
const [callRerenderOfGrid, setCallRerenderOfGrid] = useState(0);
const isTabVisible = usePageVisibility();
const refAll = useRef({
isReady: false,
layouts: {
lg: [
// { i: 'a', w: 4, h: 16, x: 22, y: 0 },
// { i: 'b', w: 5, h: 10, x: 17, y: 0 },
],
} as Layouts,
breakpoints: { lg: 100, md: 0, sm: 0, xs: 0, xxs: 0 },
cols: { lg: 26, md: 0, sm: 0, xs: 0, xxs: 0 },
containerWidth: 0,
colsPrev: 26,
needPostProcess: false,
items: [...items],
});
// TODO
// 1. onLayoutChange (original) not calling when we change x of any widget
// 2. setKey need no call rerender for update props
const onLayoutChange: ItemCallback = (newItems, _, newItem) => {
const updatedItems = newItems.map(item => {
const toLeft = (item.x + item.w / 2) / refAll.current.cols.lg <= 0.5;
const original = refAll.current.items.find(x => x.name === item.i)!;
return {
...original,
width: item.w,
height: item.h,
leftOffset: toLeft ? item.x : undefined,
rightOffset: !toLeft ? refAll.current.cols.lg - (item.x + item.w) : undefined,
topOffset: item.y,
};
});
const sortedItems = [
...updatedItems.filter(x => x.name !== newItem.i),
updatedItems.find(x => x.name === newItem.i)!,
];
refAll.current.layouts = {
lg: [...newItems.filter(x => x.i !== newItem.i), newItem],
};
onChange(sortedItems);
setKey(x => x + 1);
};
useEffect(() => {
refAll.current.items = [...items];
setKey(x => x + 1);
}, [items]);
// TODO
// 1. Unknown why but if we set layout and cols both instantly it not help...
// 1.2 it means that we should make report... until we will send new key on window resize
useEffect(() => {
const updateItems = () => {
if (!containerRef.current) {
return;
}
const { width } = containerRef.current.getBoundingClientRect();
const newColsCount = (width - (width % colSize)) / colSize;
refAll.current.layouts = {
lg: refAll.current.items.map(({ name, width, height, rightOffset, leftOffset, topOffset = 0 }) => {
return {
i: name,
x: rightOffset != null ? newColsCount - width - rightOffset : leftOffset ?? 0,
y: topOffset,
w: width,
h: height,
};
}),
};
refAll.current.cols = { lg: newColsCount, md: 0, sm: 0, xs: 0, xxs: 0 };
};
const updateContainerWidth = () => {
if (!containerRef.current) {
return;
}
const { width } = containerRef.current.getBoundingClientRect();
refAll.current.containerWidth = width;
const newColsCount = (width - (width % colSize)) / colSize;
if (width <= 100 || refAll.current.cols.lg === newColsCount) {
return false;
}
if (!refAll.current.isReady) {
updateItems();
setCallRerenderOfGrid(x => x + 1);
refAll.current.isReady = true;
return;
}
refAll.current.layouts = {
lg: refAll.current.layouts.lg.map(lgEl => {
const toLeft = (lgEl.x + lgEl.w / 2) / refAll.current.cols.lg <= 0.5;
const next = {
...lgEl,
x: toLeft ? lgEl.x : newColsCount - (refAll.current.cols.lg - lgEl.x),
};
return next;
}),
};
refAll.current.cols = { lg: newColsCount, md: 0, sm: 0, xs: 0, xxs: 0 };
setCallRerenderOfGrid(x => x + 1);
};
setTimeout(() => updateContainerWidth(), 100);
const withRerender = () => {
updateContainerWidth();
setCallRerenderOfGrid(x => x + 1);
};
window.addEventListener('resize', withRerender);
return () => {
window.removeEventListener('resize', withRerender);
};
}, []);
const isNotSet = initState.cols === refAll.current.cols.lg;
return (
<div ref={containerRef} className={clsx(classes.GridLayoutWrapper, 'relative p-4')}>
{!isNotSet && isTabVisible && (
<ResponsiveGridLayout
key={callRerenderOfGrid}
className={classes.GridLayout}
layouts={refAll.current.layouts}
breakpoints={refAll.current.breakpoints}
cols={refAll.current.cols}
rowHeight={30}
width={refAll.current.containerWidth}
preventCollision={true}
compactType={null}
allowOverlap
onDragStop={onLayoutChange}
onResizeStop={onLayoutChange}
// onResizeStart={onLayoutChange}
// onDragStart={onLayoutChange}
isBounded
containerPadding={[0, 0]}
resizeHandles={['sw', 'se']}
draggableHandle=".react-grid-dragHandleExample"
>
{refAll.current.items.map(x => (
<div key={x.name} className="grid-item">
{x.item()}
</div>
))}
</ResponsiveGridLayout>
)}
</div>
);
};

View File

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

View File

@@ -1,4 +1,5 @@
export * from './Widget';
export * from './WidgetsGrid';
export * from './SystemSettingsDialog';
export * from './SystemCustomLabelDialog';
export * from './SystemLinkSignatureDialog';

View File

@@ -1,92 +0,0 @@
import { WindowProps } from '@/hooks/Mapper/components/ui-kit/WindowManager/types.ts';
import {
LocalCharacters,
RoutesWidget,
SystemInfo,
SystemSignatures,
SystemStructures,
} from '@/hooks/Mapper/components/mapInterface/widgets';
export const CURRENT_WINDOWS_VERSION = 8;
export const WINDOWS_LOCAL_STORE_KEY = 'windows:settings:v2';
export enum WidgetsIds {
info = 'info',
signatures = 'signatures',
local = 'local',
routes = 'routes',
structures = 'structures',
}
export const STORED_VISIBLE_WIDGETS_DEFAULT = [
WidgetsIds.info,
WidgetsIds.local,
WidgetsIds.routes,
WidgetsIds.signatures,
];
export const DEFAULT_WIDGETS: WindowProps[] = [
{
id: WidgetsIds.info,
position: { x: 10, y: 10 },
size: { width: 250, height: 200 },
zIndex: 0,
content: () => <SystemInfo />,
},
{
id: WidgetsIds.signatures,
position: { x: 10, y: 220 },
size: { width: 250, height: 300 },
zIndex: 0,
content: () => <SystemSignatures />,
},
{
id: WidgetsIds.local,
position: { x: 270, y: 10 },
size: { width: 250, height: 510 },
zIndex: 0,
content: () => <LocalCharacters />,
},
{
id: WidgetsIds.routes,
position: { x: 10, y: 530 },
size: { width: 510, height: 200 },
zIndex: 0,
content: () => <RoutesWidget />,
},
{
id: WidgetsIds.structures,
position: { x: 10, y: 730 },
size: { width: 510, height: 200 },
zIndex: 0,
content: () => <SystemStructures />,
},
];
type WidgetsCheckboxesType = {
id: WidgetsIds;
label: string;
}[];
export const WIDGETS_CHECKBOXES_PROPS: WidgetsCheckboxesType = [
{
id: WidgetsIds.info,
label: 'System Info',
},
{
id: WidgetsIds.signatures,
label: 'Signatures',
},
{
id: WidgetsIds.local,
label: 'Local',
},
{
id: WidgetsIds.routes,
label: 'Routes',
},
{
id: WidgetsIds.structures,
label: 'Structures',
},
];

View File

@@ -1,4 +1,4 @@
import { useCallback, useMemo, useRef } from 'react';
import { useCallback, useMemo } from 'react';
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { VirtualScroller, VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
@@ -8,10 +8,6 @@ import { CharacterTypeRaw, WithIsOwnCharacter } from '@/hooks/Mapper/types';
import { CharacterCard, LayoutEventBlocker, WdCheckbox } from '@/hooks/Mapper/components/ui-kit';
import { sortCharacters } from '@/hooks/Mapper/components/mapInterface/helpers/sortCharacters.ts';
import useLocalStorageState from 'use-local-storage-state';
import { useMapCheckPermissions, useMapGetOption } from '@/hooks/Mapper/mapRootProvider/hooks/api';
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth.ts';
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
type CharItemProps = {
compact: boolean;
@@ -66,14 +62,6 @@ export const LocalCharacters = () => {
const [systemId] = selectedSystems;
const restrictOfflineShowing = useMapGetOption('restrict_offline_showing');
const isAdminOrManager = useMapCheckPermissions([UserPermission.MANAGE_MAP]);
const showOffline = useMemo(
() => !restrictOfflineShowing || isAdminOrManager,
[isAdminOrManager, restrictOfflineShowing],
);
const itemTemplate = useItemTemplate();
const sorted = useMemo(() => {
@@ -82,39 +70,32 @@ export const LocalCharacters = () => {
.map(x => ({ ...x, isOwn: userCharacters.includes(x.eve_id), compact: settings.compact }))
.sort(sortCharacters);
if (!showOffline || !settings.showOffline) {
if (!settings.showOffline) {
return sorted.filter(c => c.online);
}
return sorted;
// eslint-disable-next-line
}, [showOffline, characters, settings.showOffline, settings.compact, systemId, userCharacters, presentCharacters]);
}, [characters, settings.showOffline, settings.compact, systemId, userCharacters, presentCharacters]);
const isNobodyHere = sorted.length === 0;
const isNotSelectedSystem = selectedSystems.length !== 1;
const showList = sorted.length > 0 && selectedSystems.length === 1;
const ref = useRef<HTMLDivElement>(null);
const compact = useMaxWidth(ref, 145);
return (
<Widget
label={
<div className="flex justify-between items-center text-xs w-full" ref={ref}>
<div className="flex justify-between items-center text-xs w-full">
<span className="select-none">Local{showList ? ` [${sorted.length}]` : ''}</span>
<LayoutEventBlocker className="flex items-center gap-2">
{showOffline && (
<WdTooltipWrapper content="Show offline characters in system">
<WdCheckbox
size="xs"
labelSide="left"
label={compact ? '' : 'Show offline'}
value={settings.showOffline}
classNameLabel="text-stone-400 hover:text-stone-200 transition duration-300"
onChange={() => setSettings(() => ({ ...settings, showOffline: !settings.showOffline }))}
/>
</WdTooltipWrapper>
)}
<WdCheckbox
size="xs"
labelSide="left"
label={'Show offline'}
value={settings.showOffline}
classNameLabel="text-stone-400 hover:text-stone-200 transition duration-300"
onChange={() => setSettings(() => ({ ...settings, showOffline: !settings.showOffline }))}
/>
<span
className={clsx('w-4 h-4 cursor-pointer', {
@@ -134,9 +115,7 @@ export const LocalCharacters = () => {
)}
{isNobodyHere && !isNotSelectedSystem && (
<div className="w-full h-full flex justify-center items-center select-none text-stone-400/80 text-sm">
Nobody here
</div>
<div className="w-full h-full flex justify-center items-center select-none text-stone-400/80 text-sm">Nobody here</div>
)}
{showList && (

View File

@@ -8,6 +8,7 @@
.RouteSystem {
width: 8px;
height: 8px;
background: #ffffff;
cursor: pointer;
transition: opacity 200ms;

View File

@@ -5,7 +5,7 @@ import { SystemViewStandalone, WdTooltip, WdTooltipHandlers } from '@/hooks/Mapp
import { getBackgroundClass, getShapeClass } from '@/hooks/Mapper/components/map/helpers';
import { MouseEvent, useCallback, useRef, useState } from 'react';
import { Commands } from '@/hooks/Mapper/types';
import { emitMapEvent } from '@/hooks/Mapper/events';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
export type RouteSystemProps = {
destination: number;
@@ -88,10 +88,11 @@ export interface RoutesListProps {
export const RoutesList = ({ data, onContextMenu }: RoutesListProps) => {
const [selected, setSelected] = useState<number | null>(null);
const { mapRef } = useMapRootState();
const handleClick = useCallback(
(systemId: number) => emitMapEvent({ name: Commands.centerSystem, data: systemId?.toString() }),
[],
(systemId: number) => mapRef.current?.command(Commands.centerSystem, systemId.toString()),
[mapRef],
);
if (!data.has_connection) {

View File

@@ -1,11 +1,12 @@
import { Dialog } from 'primereact/dialog';
import { useCallback, useEffect, useRef, useState } from 'react';
import { Button } from 'primereact/button';
import { WdCheckbox } from '@/hooks/Mapper/components/ui-kit';
import {
RoutesType,
useRouteProvider,
} from '@/hooks/Mapper/components/mapInterface/widgets/RoutesWidget/RoutesProvider.tsx';
import { PrettySwitchbox } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/components';
import { CheckboxChangeEvent } from 'primereact/checkbox';
interface RoutesSettingsDialog {
visible: boolean;
@@ -37,8 +38,8 @@ export const RoutesSettingsDialog = ({ visible, setVisible }: RoutesSettingsDial
currentData.current = data;
const handleChangeEvent = useCallback(
(propName: keyof RoutesType) => (event: boolean) => {
optionsRef.current = { ...optionsRef.current, [propName]: event };
(propName: keyof RoutesType) => (event: CheckboxChangeEvent) => {
optionsRef.current = { ...optionsRef.current, [propName]: event.checked };
updateKey(x => x + 1);
},
[],
@@ -70,14 +71,14 @@ export const RoutesSettingsDialog = ({ visible, setVisible }: RoutesSettingsDial
setVisible(false);
}}
>
<div className="flex flex-col gap-3 p-2.5">
<div className="flex flex-col gap-2 mb-2">
<div className="flex flex-col gap-3">
<div className="flex flex-col gap-2">
{checkboxes.map(({ label, propName }) => (
<PrettySwitchbox
<WdCheckbox
key={propName}
label={label}
checked={optionsRef.current[propName]}
setChecked={handleChangeEvent(propName)}
value={optionsRef.current[propName]}
onChange={handleChangeEvent(propName)}
/>
))}
</div>

View File

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

View File

@@ -1,79 +0,0 @@
.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;
}
}
}

View File

@@ -1,11 +1,9 @@
import { Dialog } from 'primereact/dialog';
import { useCallback, useState } from 'react';
import { Button } from 'primereact/button';
import { TabPanel, TabView } from 'primereact/tabview';
import styles from './SystemSignatureSettingsDialog.module.scss';
import { PrettySwitchbox } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/components';
import { Checkbox } from 'primereact/checkbox';
export type Setting = { key: string; name: string; value: boolean; isFilter?: boolean };
export type Setting = { key: string; name: string; value: boolean };
export const COSMIC_SIGNATURE = 'Cosmic Signature';
export const COSMIC_ANOMALY = 'Cosmic Anomaly';
@@ -26,12 +24,8 @@ 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)));
};
@@ -41,45 +35,23 @@ export const SystemSignatureSettingsDialog = ({
}, [onSave, settings]);
return (
<Dialog header="System Signatures Settings" visible={true} onHide={onCancel} className="w-full max-w-lg h-[500px]">
<div className="flex flex-col gap-3 justify-between h-full">
<Dialog header="Filter signatures" visible draggable={false} style={{ width: '300px' }} onHide={onCancel}>
<div className="flex flex-col gap-3">
<div className="flex flex-col gap-2">
<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 (
<PrettySwitchbox
key={setting.key}
label={setting.name}
checked={setting.value}
setChecked={() => handleSettingsChange(setting.key)}
/>
);
})}
</div>
</TabPanel>
<TabPanel header="User Interface" headerClassName={styles.verticalTabHeader}>
<div className="w-full h-full flex flex-col gap-1">
{userSettings.map(setting => {
return (
<PrettySwitchbox
key={setting.key}
label={setting.name}
checked={setting.value}
setChecked={() => handleSettingsChange(setting.key)}
/>
);
})}
</div>
</TabPanel>
</TabView>
</div>
{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>
<div className="flex gap-2 justify-end">

View File

@@ -1,61 +1,36 @@
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
import {
InfoDrawer,
LayoutEventBlocker,
SystemView,
TooltipPosition,
WdCheckbox,
WdImgButton,
} from '@/hooks/Mapper/components/ui-kit';
import { InfoDrawer, LayoutEventBlocker, TooltipPosition, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
import { SystemSignaturesContent } from './SystemSignaturesContent';
import {
COSMIC_ANOMALY,
COSMIC_SIGNATURE,
DEPLOYABLE,
DRONE,
Setting,
SHIP,
STARBASE,
STRUCTURE,
SystemSignatureSettingsDialog,
COSMIC_SIGNATURE,
COSMIC_ANOMALY,
DEPLOYABLE,
STRUCTURE,
STARBASE,
SHIP,
DRONE,
} from './SystemSignatureSettingsDialog';
import { SignatureGroup } from '@/hooks/Mapper/types';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { PrimeIcons } from 'primereact/api';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { CheckboxChangeEvent } from 'primereact/checkbox';
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth.ts';
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
const SIGNATURE_SETTINGS_KEY = 'wanderer_system_signature_settings_v5_2';
export const SHOW_DESCRIPTION_COLUMN_SETTING = 'show_description_column_setting';
export const SHOW_UPDATED_COLUMN_SETTING = 'SHOW_UPDATED_COLUMN_SETTING';
export const LAZY_DELETE_SIGNATURES_SETTING = 'LAZY_DELETE_SIGNATURES_SETTING';
export const KEEP_LAZY_DELETE_SETTING = 'KEEP_LAZY_DELETE_ENABLED_SETTING';
const settings: Setting[] = [
{ key: SHOW_UPDATED_COLUMN_SETTING, name: 'Show Updated Column', value: false, isFilter: false },
{ key: SHOW_DESCRIPTION_COLUMN_SETTING, name: 'Show Description Column', value: false, isFilter: false },
{ key: LAZY_DELETE_SIGNATURES_SETTING, name: 'Lazy Delete Signatures', value: false, isFilter: false },
{ key: KEEP_LAZY_DELETE_SETTING, name: 'Keep "Lazy Delete" Enabled', 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 },
{ 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';
const defaultSettings = () => {
return [...settings];
};
@@ -72,25 +47,12 @@ export const SystemSignatures = () => {
const isNotSelectedSystem = selectedSystems.length !== 1;
const lazyDeleteValue = useMemo(() => {
return settings.find(setting => setting.key === LAZY_DELETE_SIGNATURES_SETTING)!.value;
}, [settings]);
const handleSettingsChange = useCallback((settings: Setting[]) => {
setSettings(settings);
localStorage.setItem(SIGNATURE_SETTINGS_KEY, JSON.stringify(settings));
setVisible(false);
}, []);
const handleLazyDeleteChange = useCallback((value: boolean) => {
setSettings(settings => {
const lazyDelete = settings.find(setting => setting.key === LAZY_DELETE_SIGNATURES_SETTING)!;
lazyDelete.value = value;
localStorage.setItem(SIGNATURE_SETTINGS_KEY, JSON.stringify(settings));
return [...settings];
});
}, []);
useEffect(() => {
const restoredSettings = localStorage.getItem(SIGNATURE_SETTINGS_KEY);
@@ -99,34 +61,13 @@ export const SystemSignatures = () => {
}
}, []);
const ref = useRef<HTMLDivElement>(null);
const compact = useMaxWidth(ref, 260);
return (
<Widget
label={
<div className="flex justify-between items-center text-xs w-full h-full" ref={ref}>
<div className="flex justify-between items-center gap-1">
{!compact && (
<div className="flex whitespace-nowrap text-ellipsis overflow-hidden text-stone-400">
Signatures {isNotSelectedSystem ? '' : 'in'}
</div>
)}
{!isNotSelectedSystem && <SystemView systemId={systemId} className="select-none text-center" hideRegion />}
</div>
<div className="flex justify-between items-center text-xs w-full h-full">
<div className="flex gap-1">System Signatures</div>
<LayoutEventBlocker className="flex gap-2.5">
<WdTooltipWrapper content="Enable Lazy delete">
<WdCheckbox
size="xs"
labelSide="left"
label={compact ? '' : 'Lazy delete'}
value={lazyDeleteValue}
classNameLabel="text-stone-400 hover:text-stone-200 transition duration-300 whitespace-nowrap text-ellipsis overflow-hidden"
onChange={(event: CheckboxChangeEvent) => handleLazyDeleteChange(!!event.checked)}
/>
</WdTooltipWrapper>
<WdImgButton
className={PrimeIcons.QUESTION_CIRCLE}
tooltip={{
@@ -150,7 +91,8 @@ 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>
<br /> and then use <b className="text-sky-500">Del</b> or{' '}
<b className="text-sky-500">Backspace</b>
</InfoDrawer>
</div>
) as React.ReactNode,
@@ -166,7 +108,7 @@ export const SystemSignatures = () => {
System is not selected
</div>
) : (
<SystemSignaturesContent systemId={systemId} settings={settings} onLazyDeleteChange={handleLazyDeleteChange} />
<SystemSignaturesContent systemId={systemId} settings={settings} />
)}
{visible && (
<SystemSignatureSettingsDialog

View File

@@ -1,11 +1,8 @@
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useClipboard } from '@/hooks/Mapper/hooks/useClipboard';
import { parseSignatures } from '@/hooks/Mapper/helpers';
import { Commands, OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
import { WdTooltip, WdTooltipHandlers } from '@/hooks/Mapper/components/ui-kit';
import {
getGroupIdByRawGroup,
GROUPS_LIST,
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
import { DataTable, DataTableRowClickEvent, DataTableRowMouseEvent, SortOrder } from 'primereact/datatable';
import { Column } from 'primereact/column';
@@ -14,7 +11,6 @@ import useRefState from 'react-usestateref';
import { Setting } from '../SystemSignatureSettingsDialog';
import { useHotkey } from '@/hooks/Mapper/hooks';
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth.ts';
import { useClipboard } from '@/hooks/Mapper/hooks/useClipboard';
import classes from './SystemSignaturesContent.module.scss';
import clsx from 'clsx';
@@ -25,55 +21,40 @@ import {
getRowColorByTimeLeft,
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/helpers';
import {
renderAddedTimeLeft,
renderDescription,
renderIcon,
renderInfoColumn,
renderUpdatedTimeLeft,
renderTimeLeft,
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders';
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,
SHOW_UPDATED_COLUMN_SETTING,
LAZY_DELETE_SIGNATURES_SETTING,
KEEP_LAZY_DELETE_SETTING,
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures';
type SystemSignaturesSortSettings = {
sortField: string;
sortOrder: SortOrder;
};
const SORT_DEFAULT_VALUES: SystemSignaturesSortSettings = {
sortField: 'inserted_at',
sortField: 'updated_at',
sortOrder: -1,
};
interface SystemSignaturesContentProps {
systemId: string;
settings: Setting[];
hideLinkedSignatures?: boolean;
selectable?: boolean;
onSelect?: (signature: SystemSignature) => void;
onLazyDeleteChange?: (value: boolean) => void;
}
export const SystemSignaturesContent = ({
systemId,
settings,
hideLinkedSignatures,
selectable,
onSelect,
onLazyDeleteChange,
}: SystemSignaturesContentProps) => {
export const SystemSignaturesContent = ({ systemId, settings, selectable, onSelect }: SystemSignaturesContentProps) => {
const { outCommand } = useMapRootState();
const [signatures, setSignatures, signaturesRef] = useRefState<SystemSignature[]>([]);
const [selectedSignatures, setSelectedSignatures] = useState<SystemSignature[]>([]);
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);
@@ -85,20 +66,10 @@ export const SystemSignaturesContent = ({
const tableRef = useRef<HTMLDivElement>(null);
const compact = useMaxWidth(tableRef, 260);
const medium = useMaxWidth(tableRef, 380);
const refData = useRef({ selectable });
refData.current = { selectable };
const tooltipRef = useRef<WdTooltipHandlers>(null);
const { clipboardContent, setClipboardContent } = useClipboard();
const lazyDeleteValue = useMemo(() => {
return settings.find(setting => setting.key === LAZY_DELETE_SIGNATURES_SETTING)?.value ?? false;
}, [settings]);
const keepLazyDeleteValue = useMemo(() => {
return settings.find(setting => setting.key === KEEP_LAZY_DELETE_SETTING)?.value ?? false;
}, [settings]);
const { clipboardContent } = useClipboard();
const handleResize = useCallback(() => {
if (tableRef.current) {
@@ -109,39 +80,13 @@ export const SystemSignaturesContent = ({
}
}, []);
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 showUpdatedColumn = useMemo(() => settings.find(s => s.key === SHOW_UPDATED_COLUMN_SETTING)?.value, [settings]);
const filteredSignatures = useMemo(() => {
return signatures
.filter(x => {
if (hideLinkedSignatures && !!x.linked_system) {
return false;
}
const isCosmicSignature = x.kind === COSMIC_SIGNATURE;
const preparedGroup = getGroupIdByRawGroup(x.group);
if (isCosmicSignature) {
const showCosmicSignatures = settings.find(y => y.key === COSMIC_SIGNATURE)?.value;
if (showCosmicSignatures) {
return !x.group || groupSettings.find(y => y.key === preparedGroup)?.value;
} else {
return !!x.group && groupSettings.find(y => y.key === preparedGroup)?.value;
}
}
return settings.find(y => y.key === x.kind)?.value;
})
.filter(x => 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, groupSettings, hideLinkedSignatures]);
}, [signatures, settings]);
const handleGetSignatures = useCallback(async () => {
const { signatures } = await outCommand({
@@ -149,17 +94,33 @@ export const SystemSignaturesContent = ({
data: { system_id: systemId },
});
setAskUser(false);
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, skipUpdateUntouched?: boolean) => {
const { added, updated, removed } = getActualSigs(
signaturesRef.current,
newSignatures,
updateOnly,
skipUpdateUntouched,
);
async (newSignatures: SystemSignature[], updateOnly: boolean) => {
const { added, updated, removed } = getActualSigs(signaturesRef.current, newSignatures, updateOnly);
const { signatures: updatedSignatures } = await outCommand({
type: OutCommand.updateSignatures,
@@ -177,32 +138,34 @@ export const SystemSignaturesContent = ({
[outCommand, systemId],
);
const handleDeleteSelected = useCallback(
async (e: KeyboardEvent) => {
if (selectable) {
return;
}
if (selectedSignatures.length === 0) {
return;
}
e.preventDefault();
e.stopPropagation();
const selectedSignaturesEveIds = selectedSignatures.map(x => x.eve_id);
await handleUpdateSignatures(
signatures.filter(x => !selectedSignaturesEveIds.includes(x.eve_id)),
false,
true,
);
},
[handleUpdateSignatures, selectable, signatures, selectedSignatures],
);
const handleDeleteSelected = useCallback(async () => {
if (selectable) {
return;
}
if (selectedSignatures.length === 0) {
return;
}
const selectedSignaturesEveIds = selectedSignatures.map(x => x.eve_id);
await handleUpdateSignatures(
signatures.filter(x => !selectedSignaturesEveIds.includes(x.eve_id)),
false,
);
}, [handleUpdateSignatures, selectable, signatures, selectedSignatures]);
const handleSelectAll = useCallback(() => {
setSelectedSignatures(signatures);
}, [signatures]);
const handleReplaceAll = useCallback(() => {
handleUpdateSignatures(parsedSignatures, false);
setAskUser(false);
}, [parsedSignatures, handleUpdateSignatures]);
const handleUpdateOnly = useCallback(() => {
handleUpdateSignatures(parsedSignatures, true);
setAskUser(false);
}, [parsedSignatures, handleUpdateSignatures]);
const handleSelectSignatures = useCallback(
// TODO still will be good to define types if we use typescript
// @ts-ignore
@@ -216,51 +179,38 @@ export const SystemSignaturesContent = ({
[onSelect, selectable],
);
const handlePaste = async (clipboardContent: string) => {
useHotkey(true, ['a'], handleSelectAll);
useHotkey(false, ['Backspace', 'Delete'], handleDeleteSelected);
useEffect(() => {
if (selectable) {
return;
}
if (!clipboardContent) {
return;
}
const newSignatures = parseSignatures(
clipboardContent,
settings.map(x => x.key),
);
handleUpdateSignatures(newSignatures, !lazyDeleteValue);
const { removed } = getActualSigs(signaturesRef.current, newSignatures, false);
if (lazyDeleteValue && !keepLazyDeleteValue) {
onLazyDeleteChange?.(false);
if (!signaturesRef.current || !signaturesRef.current.length || !removed.length) {
handleUpdateSignatures(newSignatures, false);
} else {
setParsedSignatures(newSignatures);
setAskUser(true);
}
};
const handleEnterRow = useCallback(
(e: DataTableRowMouseEvent) => {
setHoveredSig(filteredSignatures[e.index]);
tooltipRef.current?.show(e.originalEvent);
},
[filteredSignatures],
);
const handleLeaveRow = useCallback((e: DataTableRowMouseEvent) => {
tooltipRef.current?.hide(e.originalEvent);
setHoveredSig(null);
}, []);
useEffect(() => {
if (refData.current.selectable) {
return;
}
if (!clipboardContent?.text) {
return;
}
handlePaste(clipboardContent.text);
setClipboardContent(null);
}, [clipboardContent, selectable, lazyDeleteValue, keepLazyDeleteValue]);
useHotkey(true, ['a'], handleSelectAll);
useHotkey(false, ['Backspace', 'Delete'], handleDeleteSelected);
}, [clipboardContent, selectable]);
useEffect(() => {
if (!systemId) {
setSignatures([]);
setAskUser(false);
return;
}
@@ -294,6 +244,19 @@ export const SystemSignaturesContent = ({
};
}, []);
const handleEnterRow = useCallback(
(e: DataTableRowMouseEvent) => {
setHoveredSig(filteredSignatures[e.index]);
tooltipRef.current?.show(e.originalEvent);
},
[filteredSignatures],
);
const handleLeaveRow = useCallback((e: DataTableRowMouseEvent) => {
tooltipRef.current?.hide(e.originalEvent);
setHoveredSig(null);
}, []);
const renderToolbar = (/*row: SystemSignature*/) => {
return (
<div className="flex justify-end items-center gap-2 mr-[4px]">
@@ -345,7 +308,7 @@ export const SystemSignaturesContent = ({
return clsx(classes.TableRowCompact, 'bg-amber-500/50 hover:bg-amber-500/70 transition duration-200');
}
const dateClass = getRowColorByTimeLeft(row.inserted_at ? new Date(row.inserted_at) : undefined);
const dateClass = getRowColorByTimeLeft(row.updated_at ? new Date(row.updated_at) : undefined);
if (!dateClass) {
return clsx(classes.TableRowCompact, 'hover:bg-purple-400/20 transition duration-200');
}
@@ -372,56 +335,32 @@ export const SystemSignaturesContent = ({
header="Group"
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
hidden={compact}
style={{ maxWidth: 110, minWidth: 110, width: 110 }}
sortable
></Column>
<Column
field="info"
// header="Info"
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
body={renderInfoColumn}
style={{ maxWidth: nameColumnWidth }}
hidden={compact || medium}
></Column>
{showDescriptionColumn && (
<Column
field="description"
header="Description"
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
body={renderDescription}
hidden={compact}
sortable
></Column>
)}
<Column
field="inserted_at"
header="Added"
field="updated_at"
header="Updated"
dataType="date"
bodyClassName="w-[70px] text-ellipsis overflow-hidden whitespace-nowrap"
body={renderAddedTimeLeft}
body={renderTimeLeft}
sortable
></Column>
{showUpdatedColumn && (
<Column
field="updated_at"
header="Updated"
dataType="date"
bodyClassName="w-[70px] text-ellipsis overflow-hidden whitespace-nowrap"
body={renderUpdatedTimeLeft}
sortable
></Column>
)}
{!selectable && (
<Column
bodyClassName="p-0 pl-1 pr-2"
field="group"
body={renderToolbar}
// headerClassName={headerClasses}
style={{ maxWidth: 26, minWidth: 26, width: 26 }}
></Column>
)}
<Column
bodyClassName="p-0 pl-1 pr-2"
field="group"
body={renderToolbar}
// headerClassName={headerClasses}
style={{ maxWidth: 26, minWidth: 26, width: 26 }}
></Column>
</DataTable>
</>
)}
@@ -431,13 +370,32 @@ export const SystemSignaturesContent = ({
content={hoveredSig ? <SignatureView {...hoveredSig} /> : null}
/>
{showSignatureSettings && (
<SignatureSettings
systemId={systemId}
show
onHide={() => setShowSignatureSettings(false)}
signatureData={selectedSignature}
/>
<SignatureSettings
systemId={systemId}
show={showSignatureSettings}
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">
<div className="text-stone-400/80 text-sm">
<div className="flex flex-col text-center gap-2">
<button className="p-button p-component p-button-outlined p-button-sm btn-wide">
<span className="p-button-label p-c" onClick={handleUpdateOnly}>
Update
</span>
</button>
<button className="p-button p-component p-button-outlined p-button-sm btn-wide">
<span className="p-button-label p-c" onClick={handleReplaceAll}>
Update & Delete missing
</span>
</button>
</div>
</div>
</div>
</div>
)}
</div>
</>

View File

@@ -1,12 +1,4 @@
import {
GroupType,
SignatureGroup,
SignatureGroupENG,
SignatureGroupRU,
SignatureKind,
SignatureKindENG,
SignatureKindRU,
} from '@/hooks/Mapper/types';
import { GroupType, SignatureGroup } from '@/hooks/Mapper/types';
export const TIME_ONE_MINUTE = 1000 * 60;
export const TIME_TEN_MINUTES = 1000 * 60 * 10;
@@ -32,43 +24,3 @@ export const GROUPS: Record<SignatureGroup, GroupType> = {
[SignatureGroup.Wormhole]: { id: SignatureGroup.Wormhole, icon: '/icons/brackets/wormhole.png', ...wh },
[SignatureGroup.CosmicSignature]: { id: SignatureGroup.CosmicSignature, icon: '/icons/x_close14.png', w: 9, h: 9 },
};
export const MAPPING_GROUP_TO_ENG = {
// ENGLISH
[SignatureGroupENG.GasSite]: SignatureGroup.GasSite,
[SignatureGroupENG.RelicSite]: SignatureGroup.RelicSite,
[SignatureGroupENG.DataSite]: SignatureGroup.DataSite,
[SignatureGroupENG.OreSite]: SignatureGroup.OreSite,
[SignatureGroupENG.CombatSite]: SignatureGroup.CombatSite,
[SignatureGroupENG.Wormhole]: SignatureGroup.Wormhole,
[SignatureGroupENG.CosmicSignature]: SignatureGroup.CosmicSignature,
// RUSSIAN
[SignatureGroupRU.GasSite]: SignatureGroup.GasSite,
[SignatureGroupRU.RelicSite]: SignatureGroup.RelicSite,
[SignatureGroupRU.DataSite]: SignatureGroup.DataSite,
[SignatureGroupRU.OreSite]: SignatureGroup.OreSite,
[SignatureGroupRU.CombatSite]: SignatureGroup.CombatSite,
[SignatureGroupRU.Wormhole]: SignatureGroup.Wormhole,
[SignatureGroupRU.CosmicSignature]: SignatureGroup.CosmicSignature,
};
export const MAPPING_TYPE_TO_ENG = {
// ENGLISH
[SignatureKindENG.CosmicSignature]: SignatureKind.CosmicSignature,
[SignatureKindENG.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
[SignatureKindENG.Structure]: SignatureKind.Structure,
[SignatureKindENG.Ship]: SignatureKind.Ship,
[SignatureKindENG.Deployable]: SignatureKind.Deployable,
[SignatureKindENG.Drone]: SignatureKind.Drone,
// RUSSIAN
[SignatureKindRU.CosmicSignature]: SignatureKind.CosmicSignature,
[SignatureKindRU.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
[SignatureKindRU.Structure]: SignatureKind.Structure,
[SignatureKindRU.Ship]: SignatureKind.Ship,
[SignatureKindRU.Deployable]: SignatureKind.Deployable,
[SignatureKindRU.Drone]: SignatureKind.Drone,
};
export const getGroupIdByRawGroup = (val: string) => MAPPING_GROUP_TO_ENG[val as SignatureGroup];

View File

@@ -6,7 +6,6 @@ export const getActualSigs = (
oldSignatures: SystemSignature[],
newSignatures: SystemSignature[],
updateOnly: boolean,
skipUpdateUntouched?: boolean,
): { added: SystemSignature[]; updated: SystemSignature[]; removed: SystemSignature[] } => {
const updated: SystemSignature[] = [];
const removed: SystemSignature[] = [];
@@ -20,8 +19,6 @@ export const getActualSigs = (
const isNeedUpgrade = getState(GROUPS_LIST, newSig) > getState(GROUPS_LIST, oldSig);
if (isNeedUpgrade) {
updated.push({ ...oldSig, group: newSig.group, name: newSig.name });
} else if (!skipUpdateUntouched) {
updated.push({ ...oldSig });
}
} else {
if (!updateOnly) {

View File

@@ -1,7 +1,5 @@
export * from './renderIcon';
export * from './renderDescription';
export * from './renderName';
export * from './renderAddedTimeLeft';
export * from './renderUpdatedTimeLeft';
export * from './renderTimeLeft';
export * from './renderLinkedSystem';
export * from './renderInfoColumn';

View File

@@ -1,10 +0,0 @@
import { SystemSignature } from '@/hooks/Mapper/types';
import { TimeLeft } from '@/hooks/Mapper/components/ui-kit';
export const renderAddedTimeLeft = (row: SystemSignature) => {
return (
<div className="flex w-full items-center">
<TimeLeft cDate={row.inserted_at ? new Date(row.inserted_at) : undefined} />
</div>
);
};

View File

@@ -1,5 +0,0 @@
import { SystemSignature } from '@/hooks/Mapper/types';
export const renderDescription = (row: SystemSignature) => {
return <span title={row?.description}>{row?.description}</span>;
};

View File

@@ -0,0 +1,3 @@
.whFontSize {
font-size: 11px !important;
}

View File

@@ -1,32 +1,18 @@
import { PrimeIcons } from 'primereact/api';
import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
import { SystemViewStandalone, WHClassView } from '@/hooks/Mapper/components/ui-kit';
import {
k162Types,
renderK162Type,
} from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureK162TypeSelect';
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
import clsx from 'clsx';
import { renderName } from './renderName.tsx';
import classes from './renderInfoColumn.module.scss';
export const renderInfoColumn = (row: SystemSignature) => {
if (!row.group || row.group === SignatureGroup.Wormhole) {
let k162TypeOption = null;
if (row.custom_info) {
const customInfo = JSON.parse(row.custom_info);
if (customInfo.k162Type) {
k162TypeOption = k162Types.find(x => x.value === customInfo.k162Type);
}
}
return (
<div className="flex justify-start items-center gap-[4px]">
<div className="flex justify-start items-center gap-[6px]">
{row.type && (
<WHClassView
className="text-[11px]"
classNameWh="!text-[11px] !font-bold"
classNameWh={classes.whFontSize}
highlightName
hideWhClass={!!row.linked_system}
whClassName={row.type}
noOffset
@@ -34,10 +20,9 @@ export const renderInfoColumn = (row: SystemSignature) => {
/>
)}
{!row.linked_system && row.type === 'K162' && !!k162TypeOption && <>{renderK162Type(k162TypeOption)}</>}
{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')}
@@ -47,23 +32,13 @@ export const renderInfoColumn = (row: SystemSignature) => {
</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>
);
if (row.description != null && row.description.length > 0) {
return <span title={row.description}>{row.description}</span>;
}
return renderName(row);
};

View File

@@ -1,7 +1,7 @@
import { SystemSignature } from '@/hooks/Mapper/types';
import { TimeLeft } from '@/hooks/Mapper/components/ui-kit';
export const renderUpdatedTimeLeft = (row: SystemSignature) => {
export const renderTimeLeft = (row: SystemSignature) => {
return (
<div className="flex w-full items-center">
<TimeLeft cDate={row.updated_at ? new Date(row.updated_at) : undefined} />

View File

@@ -1,118 +0,0 @@
import React, { useCallback, ClipboardEvent, useRef } from 'react';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth';
import {
LayoutEventBlocker,
WdImgButton,
TooltipPosition,
InfoDrawer,
SystemView,
} from '@/hooks/Mapper/components/ui-kit';
import { PrimeIcons } from 'primereact/api';
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
import { SystemStructuresContent } from './SystemStructuresContent/SystemStructuresContent';
import { useSystemStructures } from './hooks/useSystemStructures';
import { processSnippetText } from './helpers';
export const SystemStructures: React.FC = () => {
const {
data: { selectedSystems },
outCommand,
} = useMapRootState();
const [systemId] = selectedSystems;
const isNotSelectedSystem = selectedSystems.length !== 1;
const { structures, handleUpdateStructures } = useSystemStructures({ systemId, outCommand });
const labelRef = useRef<HTMLDivElement>(null);
const isCompact = useMaxWidth(labelRef, 260);
const processClipboard = useCallback(
(text: string) => {
const updated = processSnippetText(text, structures);
handleUpdateStructures(updated);
},
[structures, handleUpdateStructures],
);
const handlePaste = useCallback(
(e: ClipboardEvent<HTMLDivElement>) => {
e.preventDefault();
processClipboard(e.clipboardData.getData('text'));
},
[processClipboard],
);
const handlePasteTimer = useCallback(async () => {
try {
const text = await navigator.clipboard.readText();
processClipboard(text);
} catch (err) {
console.error('Clipboard read error:', err);
}
}, [processClipboard]);
function renderWidgetLabel() {
return (
<div className="flex justify-between items-center text-xs w-full h-full" ref={labelRef}>
<div className="flex justify-between items-center gap-1">
{!isCompact && (
<div className="flex whitespace-nowrap text-ellipsis overflow-hidden text-stone-400">
Structures
{!isNotSelectedSystem && ' in'}
</div>
)}
{!isNotSelectedSystem && <SystemView systemId={systemId} className="select-none text-center" hideRegion />}
</div>
<LayoutEventBlocker className="flex gap-2.5">
<WdImgButton
className={`${PrimeIcons.CLOCK} text-sky-400 hover:text-sky-200 transition duration-300`}
onClick={handlePasteTimer}
tooltip={{
position: TooltipPosition.left,
// @ts-ignore
content: 'Add Structures/Timer',
}}
/>
<WdImgButton
className={PrimeIcons.QUESTION_CIRCLE}
tooltip={{
position: TooltipPosition.left,
// @ts-ignore
content: (
<div className="flex flex-col gap-1">
<InfoDrawer title={<b className="text-slate-50">How to add/update structures?</b>}>
In game, select one or more structures in D-Scan and then
<br />
use the blue add structure data button
</InfoDrawer>
<InfoDrawer title={<b className="text-slate-50">How to add a timer?</b>}>
In game, select a structure with an active timer, right click to copy, and then
<span className="text-blue-500"> blue </span>
use the blue add structure data button
</InfoDrawer>
</div>
),
}}
/>
</LayoutEventBlocker>
</div>
);
}
return (
<div tabIndex={0} onPaste={handlePaste} className="h-full flex flex-col" style={{ outline: 'none' }}>
<Widget label={renderWidgetLabel()}>
{isNotSelectedSystem ? (
<div className="w-full h-full flex justify-center items-center select-none text-center text-stone-400/80 text-sm">
System is not selected
</div>
) : (
<SystemStructuresContent structures={structures} onUpdateStructures={handleUpdateStructures} />
)}
</Widget>
</div>
);
};

View File

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

View File

@@ -1,170 +0,0 @@
import React, { useState, useCallback, useMemo } from 'react';
import { DataTable, DataTableRowClickEvent } from 'primereact/datatable';
import { Column } from 'primereact/column';
import { PrimeIcons } from 'primereact/api';
import clsx from 'clsx';
import { SystemStructuresDialog } from '../SystemStructuresDialog/SystemStructuresDialog';
import { StructureItem } from '../helpers/structureTypes';
import { useHotkey } from '@/hooks/Mapper/hooks';
import classes from './SystemStructuresContent.module.scss';
import { renderOwnerCell, renderTypeCell, renderTimerCell } from '../renders/cellRenders';
interface SystemStructuresContentProps {
structures: StructureItem[];
onUpdateStructures: (newList: StructureItem[]) => void;
}
export const SystemStructuresContent: React.FC<SystemStructuresContentProps> = ({ structures, onUpdateStructures }) => {
const [selectedRow, setSelectedRow] = useState<StructureItem | null>(null);
const [editingItem, setEditingItem] = useState<StructureItem | null>(null);
const [showEditDialog, setShowEditDialog] = useState(false);
const handleRowClick = (e: DataTableRowClickEvent) => {
const row = e.data as StructureItem;
setSelectedRow(prev => (prev?.id === row.id ? null : row));
};
const handleRowDoubleClick = (e: DataTableRowClickEvent) => {
setEditingItem(e.data as StructureItem);
setShowEditDialog(true);
};
// Press Delete => remove selected row
const handleDeleteSelected = useCallback(
(e: KeyboardEvent) => {
if (!selectedRow) return;
e.preventDefault();
e.stopPropagation();
const newList = structures.filter(s => s.id !== selectedRow.id);
onUpdateStructures(newList);
setSelectedRow(null);
},
[selectedRow, structures, onUpdateStructures],
);
useHotkey(false, ['Delete', 'Backspace'], handleDeleteSelected);
const visibleStructures = useMemo(() => {
return structures;
}, [structures]);
return (
<div className="flex flex-col gap-2 p-2 text-xs text-stone-200 h-full">
{visibleStructures.length === 0 ? (
<div className="flex-1 flex justify-center items-center text-stone-400/80 text-sm">No structures</div>
) : (
<div className="flex-1">
<DataTable
value={visibleStructures}
dataKey="id"
className={clsx(classes.Table, 'w-full select-none h-full')}
size="small"
sortMode="single"
rowHover
style={{ tableLayout: 'fixed', width: '100%' }}
onRowClick={handleRowClick}
onRowDoubleClick={handleRowDoubleClick}
rowClassName={rowData => {
const isSelected = selectedRow?.id === rowData.id;
return clsx(
classes.TableRowCompact,
'transition-colors duration-200 cursor-pointer',
isSelected ? 'bg-amber-500/50 hover:bg-amber-500/70' : 'hover:bg-purple-400/20',
);
}}
>
<Column
header="Type"
body={renderTypeCell}
style={{
width: '160px',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
/>
<Column
field="name"
header="Name"
style={{
width: '120px',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
/>
<Column
header="Owner"
body={renderOwnerCell}
style={{
width: '120px',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
/>
<Column
field="status"
header="Status"
style={{
width: '100px',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
/>
<Column
header="Timer"
body={renderTimerCell}
style={{
width: '110px',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
/>
<Column
body={(rowData: StructureItem) => (
<i
className={clsx(PrimeIcons.PENCIL, 'text-[14px] cursor-pointer')}
title="Edit"
onClick={() => {
setEditingItem(rowData);
setShowEditDialog(true);
}}
/>
)}
style={{
width: '40px',
textAlign: 'center',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
/>
</DataTable>
</div>
)}
{showEditDialog && editingItem && (
<SystemStructuresDialog
visible={showEditDialog}
structure={editingItem}
onClose={() => setShowEditDialog(false)}
onSave={(updatedItem: StructureItem) => {
const newList = structures.map(s => (s.id === updatedItem.id ? updatedItem : s));
onUpdateStructures(newList);
setShowEditDialog(false);
}}
onDelete={(deleteId: string) => {
const newList = structures.filter(s => s.id !== deleteId);
onUpdateStructures(newList);
setShowEditDialog(false);
}}
/>
)}
</div>
);
};

View File

@@ -1,31 +0,0 @@
.systemStructureDialog {
.p-dialog-content {
background-color: var(--surface-800) !important;
}
.p-dialog-header {
background-color: var(--surface-700);
color: var(--text-color);
}
.p-dialog-header-icon,
.p-dialog-header-title {
color: var(--gray-200);
}
.p-inputtext {
background-color: #2a2a2a !important;
color: #ddd !important;
font-size: 12px !important;
padding: 0.25rem 0.5rem !important;
}
.p-dialog-footer {
.p-button {
font-size: 12px !important;
padding: 0.3rem 0.75rem !important;
}
}
}

View File

@@ -1,235 +0,0 @@
import React, { useEffect, useState, useCallback } from 'react';
import { Dialog } from 'primereact/dialog';
import { Button } from 'primereact/button';
import { AutoComplete } from 'primereact/autocomplete';
import { Calendar } from 'primereact/calendar';
import clsx from 'clsx';
import { StructureItem, StructureStatus, statusesRequiringTimer, formatToISO } from '../helpers';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { OutCommand } from '@/hooks/Mapper/types';
interface StructuresEditDialogProps {
visible: boolean;
structure?: StructureItem;
onClose: () => void;
onSave: (updatedItem: StructureItem) => void;
onDelete: (id: string) => void;
}
export const SystemStructuresDialog: React.FC<StructuresEditDialogProps> = ({
visible,
structure,
onClose,
onSave,
onDelete,
}) => {
const [editData, setEditData] = useState<StructureItem | null>(null);
const [ownerInput, setOwnerInput] = useState('');
const [ownerSuggestions, setOwnerSuggestions] = useState<{ label: string; value: string }[]>([]);
const { outCommand } = useMapRootState();
const [prevQuery, setPrevQuery] = useState('');
const [prevResults, setPrevResults] = useState<{ label: string; value: string }[]>([]);
useEffect(() => {
if (structure) {
setEditData(structure);
setOwnerInput(structure.ownerName ?? '');
} else {
setEditData(null);
setOwnerInput('');
}
}, [structure]);
// Searching corporation owners via auto-complete
const searchOwners = useCallback(
async (e: { query: string }) => {
const newQuery = e.query.trim();
if (!newQuery) {
setOwnerSuggestions([]);
return;
}
// If user typed more text but we have partial match in prevResults
if (newQuery.startsWith(prevQuery) && prevResults.length > 0) {
const filtered = prevResults.filter(item =>
item.label.toLowerCase().includes(newQuery.toLowerCase()),
);
setOwnerSuggestions(filtered);
return;
}
try {
const { results = [] } = await outCommand({
type: OutCommand.getCorporationNames,
data: { search: newQuery },
});
setOwnerSuggestions(results);
setPrevQuery(newQuery);
setPrevResults(results);
} catch (err) {
console.error('Failed to fetch owners:', err);
setOwnerSuggestions([]);
}
},
[prevQuery, prevResults, outCommand],
);
const handleChange = (field: keyof StructureItem, val: string | Date) => {
// If we want to forbid changing structureTypeId or structureType from the dialog, do so here:
if (field === 'structureTypeId' || field === 'structureType') return;
setEditData(prev => {
if (!prev) return null;
// If this is the endTime (Date from Calendar), we store as ISO or string:
if (field === 'endTime' && val instanceof Date) {
return { ...prev, endTime: val.toISOString() };
}
return { ...prev, [field]: val };
});
};
// when user picks a corp from auto-complete
const handleSelectOwner = (selected: { label: string; value: string }) => {
setOwnerInput(selected.label);
setEditData(prev =>
prev ? { ...prev, ownerName: selected.label, ownerId: selected.value } : null,
);
};
const handleStatusChange = (val: string) => {
setEditData(prev => {
if (!prev) return null;
const newStatus = val as StructureStatus;
// If new status doesn't require a timer, we clear out endTime
const newEndTime = statusesRequiringTimer.includes(newStatus) ? prev.endTime : '';
return { ...prev, status: newStatus, endTime: newEndTime };
});
};
const handleSaveClick = async () => {
if (!editData) return;
// If status doesn't require a timer, clear endTime
if (!statusesRequiringTimer.includes(editData.status)) {
editData.endTime = '';
} else if (editData.endTime) {
// convert to full ISO if not already
editData.endTime = formatToISO(editData.endTime);
}
// fetch corporation ticker if we have an ownerId
if (editData.ownerId) {
try {
const { ticker } = await outCommand({
type: OutCommand.getCorporationTicker,
data: { corp_id: editData.ownerId },
});
editData.ownerTicker = ticker ?? '';
} catch (err) {
console.error('Failed to fetch ticker:', err);
editData.ownerTicker = '';
}
}
onSave(editData);
};
const handleDeleteClick = () => {
if (!editData) return;
onDelete(editData.id);
onClose();
};
if (!editData) return null;
return (
<Dialog
visible={visible}
onHide={onClose}
header={`Edit Structure - ${editData.name ?? ''}`}
className={clsx('myStructuresDialog', 'text-stone-200 w-full max-w-md')}
>
<div className="flex flex-col gap-2 text-[14px]">
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center">
<span>Type:</span>
<input
readOnly
className="p-inputtext p-component cursor-not-allowed"
value={editData.structureType ?? ''}
/>
</label>
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center">
<span>Name:</span>
<input
className="p-inputtext p-component"
value={editData.name ?? ''}
onChange={e => handleChange('name', e.target.value)}
/>
</label>
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center">
<span>Owner:</span>
<AutoComplete
id="owner"
value={ownerInput}
suggestions={ownerSuggestions}
completeMethod={searchOwners}
minLength={3}
delay={400}
field="label"
placeholder="Corporation name..."
onChange={e => setOwnerInput(e.value)}
onSelect={e => handleSelectOwner(e.value)}
/>
</label>
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center">
<span>Status:</span>
<select
className="p-inputtext p-component"
value={editData.status}
onChange={e => handleStatusChange(e.target.value)}
>
<option value="Powered">Powered</option>
<option value="Anchoring">Anchoring</option>
<option value="Unanchoring">Unanchoring</option>
<option value="Low Power">Low Power</option>
<option value="Abandoned">Abandoned</option>
<option value="Reinforced">Reinforced</option>
</select>
</label>
{statusesRequiringTimer.includes(editData.status) && (
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center">
<span>Timer <br /> (Eve Time):</span>
<Calendar
value={editData.endTime ? new Date(editData.endTime) : undefined}
onChange={(e) => handleChange('endTime', e.value ?? '')}
showTime
hourFormat="24"
dateFormat="yy-mm-dd"
showIcon
/>
</label>
)}
<label className="grid grid-cols-[100px_1fr] gap-2 items-start mt-2">
<span className="mt-1">Notes:</span>
<textarea
className="p-inputtext p-component resize-none h-24"
value={editData.notes ?? ''}
onChange={e => handleChange('notes', e.target.value)}
/>
</label>
</div>
<div className="flex justify-end items-center gap-2 mt-4">
<Button label="Delete" severity="danger" className="p-button-sm" onClick={handleDeleteClick} />
<Button label="Save" className="p-button-sm" onClick={handleSaveClick} />
</div>
</Dialog>
);
};

View File

@@ -1,4 +0,0 @@
export * from './parserHelper';
export * from './pasteParser';
export * from './structureTypes';
export * from './structureUtils';

View File

@@ -1,92 +0,0 @@
import { StructureStatus, StructureItem, STRUCTURE_TYPE_MAP } from './structureTypes';
import { formatToISO } from './structureUtils';
// Up to you if you'd like to keep a separate constant here or not
export const statusesRequiringTimer: StructureStatus[] = ['Anchoring', 'Reinforced'];
/**
* parseFormatOneLine(line):
* - Splits by tabs
* - First col => structureTypeId
* - Second col => rawName
* - Third col => structureTypeName
*/
export function parseFormatOneLine(line: string): StructureItem | null {
const columns = line
.split('\t')
.map(c => c.trim())
.filter(Boolean);
// Expecting e.g. "35832 J214811 - SomeName Astrahus"
if (columns.length < 3) {
return null;
}
const [rawTypeId, rawName, rawTypeName] = columns;
if (columns.length != 4) {
return null;
}
if (!STRUCTURE_TYPE_MAP[rawTypeId]) {
return null;
}
if (rawTypeName != STRUCTURE_TYPE_MAP[rawTypeId]) {
return null;
}
const name = rawName.replace(/^J\d{6}\s*-\s*/, '').trim();
return {
id: crypto.randomUUID(),
structureTypeId: rawTypeId,
structureType: rawTypeName,
name,
ownerName: '',
notes: '',
status: 'Powered', // Default
endTime: '', // No timer by default
};
}
export function matchesThreeLineSnippet(lines: string[]): boolean {
if (lines.length < 3) return false;
return /until\s+\d{4}\.\d{2}\.\d{2}/i.test(lines[2]);
}
/**
* parseThreeLineSnippet:
* - Example lines:
* line1: "J214811 - Folgers"
* line2: "1,475 km"
* line3: "Reinforced until 2025.01.13 23:51"
*/
export function parseThreeLineSnippet(lines: string[]): StructureItem {
const [line1, , line3] = lines;
let status: StructureStatus = 'Reinforced';
let endTime: string | undefined;
// e.g. "Reinforced until 2025.01.13 23:27"
const match = line3.match(/^(?<stat>\w+)\s+until\s+(?<dateTime>[\d.]+\s+[\d:]+)/i);
if (match?.groups?.stat) {
const candidateStatus = match.groups.stat as StructureStatus;
if (statusesRequiringTimer.includes(candidateStatus)) {
status = candidateStatus;
}
}
if (match?.groups?.dateTime) {
let dt = match.groups.dateTime.trim().replace(/\./g, '-'); // "2025-01-13 23:27"
dt = dt.replace(' ', 'T'); // "2025-01-13T23:27"
endTime = formatToISO(dt); // => "2025-01-13T23:27:00Z"
}
return {
id: crypto.randomUUID(),
name: line1.replace(/^J\d{6}\s*-\s*/, '').trim(),
status,
endTime,
};
}

View File

@@ -1,56 +0,0 @@
import { StructureItem } from './structureTypes';
import { parseThreeLineSnippet, parseFormatOneLine, matchesThreeLineSnippet } from './parserHelper';
export function processSnippetText(rawText: string, existingStructures: StructureItem[]): StructureItem[] {
if (!rawText) {
return existingStructures.slice();
}
const lines = rawText
.split(/\r?\n/)
.map(line => line.trim())
.filter(Boolean);
if (lines.length === 3 && matchesThreeLineSnippet(lines)) {
return applyThreeLineSnippet(lines, existingStructures);
} else {
return applySingleLineParse(lines, existingStructures);
}
}
function applyThreeLineSnippet(snippetLines: string[], existingStructures: StructureItem[]): StructureItem[] {
const updatedList = [...existingStructures];
const snippetItem = parseThreeLineSnippet(snippetLines);
const existingIndex = updatedList.findIndex(s => s.name.trim() === snippetItem.name.trim());
if (existingIndex !== -1) {
const existing = updatedList[existingIndex];
updatedList[existingIndex] = {
...existing,
status: snippetItem.status,
endTime: snippetItem.endTime,
};
}
return updatedList;
}
function applySingleLineParse(lines: string[], existingStructures: StructureItem[]): StructureItem[] {
const updatedList = [...existingStructures];
const newItems: StructureItem[] = [];
for (const line of lines) {
const item = parseFormatOneLine(line);
if (!item) continue;
const isDuplicate = updatedList.some(
s => s.structureTypeId === item.structureTypeId && s.name.trim() === item.name.trim(),
);
if (!isDuplicate) {
newItems.push(item);
}
}
return [...updatedList, ...newItems];
}

View File

@@ -1,32 +0,0 @@
export type StructureStatus = 'Powered' | 'Anchoring' | 'Unanchoring' | 'Low Power' | 'Abandoned' | 'Reinforced';
export interface StructureItem {
id: string;
systemId?: string;
structureTypeId?: string;
structureType?: string;
name: string;
ownerName?: string;
ownerId?: string;
ownerTicker?: string;
notes?: string;
status: StructureStatus;
endTime?: string;
}
export const STRUCTURE_TYPE_MAP: Record<string, string> = {
'35825': 'Raitaru',
'35826': 'Azbel',
'35827': 'Sotiyo',
'35832': 'Astrahus',
'35833': 'Fortizar',
'35834': 'Keepstar',
'35835': 'Athanor',
'35836': 'Tatara',
'40340': 'Upwell Palatine Keepstar',
'47512': "'Moreau' Fortizar",
'47513': "'Draccous' Fortizar",
'47514': "'Horizon' Fortizar",
'47515': "'Marginis' Fortizar",
'47516': "'Prometheus' Fortizar",
};

View File

@@ -1,59 +0,0 @@
import { StructureItem } from './structureTypes';
export function getActualStructures(oldList: StructureItem[], newList: StructureItem[]) {
const oldMap = new Map(oldList.map(s => [s.id, s]));
const newMap = new Map(newList.map(s => [s.id, s]));
const added: StructureItem[] = [];
const updated: StructureItem[] = [];
const removed: StructureItem[] = [];
for (const newItem of newList) {
const oldItem = oldMap.get(newItem.id);
if (!oldItem) {
added.push(newItem);
} else if (JSON.stringify(oldItem) !== JSON.stringify(newItem)) {
updated.push(newItem);
}
}
for (const oldItem of oldList) {
if (!newMap.has(oldItem.id)) {
removed.push(oldItem);
}
}
return { added, updated, removed };
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function mapServerStructure(serverData: any): StructureItem {
const { owner_id, owner_ticker, structure_type_id, structure_type, owner_name, end_time, system_id, ...rest } =
serverData;
return {
...rest,
ownerId: owner_id,
ownerTicker: owner_ticker,
ownerName: owner_name,
structureType: structure_type,
structureTypeId: structure_type_id,
endTime: end_time ?? '',
systemId: system_id,
};
}
export function formatToISO(datetimeLocal: string): string {
if (!datetimeLocal) return '';
// If missing seconds, add :00
let iso = datetimeLocal;
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(iso)) {
iso += ':00';
}
// Ensure trailing 'Z'
if (!iso.endsWith('Z')) {
iso += 'Z';
}
return iso;
}

View File

@@ -1,85 +0,0 @@
import { useEffect, useState, useCallback } from 'react';
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
import { mapServerStructure, getActualStructures, StructureItem, statusesRequiringTimer } from '../helpers';
interface UseSystemStructuresProps {
systemId: string | undefined;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
outCommand: (payload: any) => Promise<any>;
}
export function useSystemStructures({ systemId, outCommand }: UseSystemStructuresProps) {
const [structures, setStructures] = useState<StructureItem[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetchStructures = useCallback(async () => {
if (!systemId) {
setStructures([]);
return;
}
setIsLoading(true);
setError(null);
try {
const { structures: fetched = [] } = await outCommand({
type: OutCommand.getStructures,
data: { system_id: systemId },
});
const mappedStructures = fetched.map(mapServerStructure);
setStructures(mappedStructures);
} catch (err) {
console.error('Failed to get structures:', err);
setError('Error fetching structures');
} finally {
setIsLoading(false);
}
}, [systemId, outCommand]);
useEffect(() => {
fetchStructures();
}, [fetchStructures]);
const sanitizeEndTimers = useCallback((item: StructureItem) => {
if (!statusesRequiringTimer.includes(item.status)) {
item.endTime = '';
}
return item;
}, []);
const sanitizeIds = useCallback((item: StructureItem) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { id, ...rest } = item;
return rest;
}, []);
const handleUpdateStructures = useCallback(
async (newList: StructureItem[]) => {
const { added, updated, removed } = getActualStructures(structures, newList);
const sanitizedAdded = added.map(sanitizeIds);
const sanitizedUpdated = updated.map(sanitizeEndTimers);
try {
const { structures: updatedStructures = [] } = await outCommand({
type: OutCommand.updateStructures,
data: {
system_id: systemId,
added: sanitizedAdded,
updated: sanitizedUpdated,
removed,
},
});
const finalStructures = updatedStructures.map(mapServerStructure);
setStructures(finalStructures);
} catch (err) {
console.error('Failed to update structures:', err);
}
},
[structures, systemId, outCommand, sanitizeIds, sanitizeEndTimers],
);
return { structures, handleUpdateStructures, isLoading, error };
}

View File

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

View File

@@ -1,50 +0,0 @@
// File: TimerCell.tsx
import React, { useEffect, useState } from 'react';
import { StructureStatus } from '../helpers/structureTypes';
import { statusesRequiringTimer } from '../helpers';
interface TimerCellProps {
endTime?: string;
status: StructureStatus;
}
function TimerCellImpl({ endTime, status }: TimerCellProps) {
const [now, setNow] = useState(() => Date.now());
useEffect(() => {
if (!endTime || !statusesRequiringTimer.includes(status)) {
return;
}
const intervalId = setInterval(() => {
setNow(Date.now());
}, 1000);
return () => clearInterval(intervalId);
}, [endTime, status]);
if (!statusesRequiringTimer.includes(status)) {
return <span className="text-stone-400"></span>;
}
if (!endTime) {
return <span className="text-sky-400">Set Timer</span>;
}
const msLeft = new Date(endTime).getTime() - now;
if (msLeft <= 0) {
return <span className="text-red-500">00:00:00</span>;
}
const sec = Math.floor(msLeft / 1000) % 60;
const min = Math.floor(msLeft / (1000 * 60)) % 60;
const hr = Math.floor(msLeft / (1000 * 3600));
const pad = (n: number) => n.toString().padStart(2, '0');
return (
<span className="text-sky-400">
{pad(hr)}:{pad(min)}:{pad(sec)}
</span>
);
}
export const TimerCell = React.memo(TimerCellImpl);

View File

@@ -1,36 +0,0 @@
import { StructureItem } from '../helpers';
import { TimerCell } from './TimerCell';
export function renderTimerCell(row: StructureItem) {
return <TimerCell endTime={row.endTime} status={row.status} />;
}
export function renderOwnerCell(row: StructureItem) {
return (
<div className="flex items-center gap-2">
{row.ownerId && (
<img
src={`https://images.evetech.net/corporations/${row.ownerId}/logo?size=32`}
alt="corp icon"
className="w-5 h-5 object-contain"
/>
)}
<span>{row.ownerTicker || row.ownerName}</span>
</div>
);
}
export function renderTypeCell(row: StructureItem) {
return (
<div className="flex items-center gap-1">
{row.structureTypeId && (
<img
src={`https://images.evetech.net/types/${row.structureTypeId}/icon`}
alt="icon"
className="w-5 h-5 object-contain"
/>
)}
<span>{row.structureType ?? ''}</span>
</div>
);
}

View File

@@ -2,4 +2,3 @@ export * from './LocalCharacters';
export * from './SystemInfo';
export * from './RoutesWidget';
export * from './SystemSignatures';
export * from './SystemStructures';

Some files were not shown because too many files have changed in this diff Show More