mirror of
				https://github.com/wanderer-industries/wanderer
				synced 2025-11-04 00:14:52 +00:00 
			
		
		
		
	Compare commits
	
		
			259 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					da5afcc91c | ||
| 
						 | 
					0002979fda | ||
| 
						 | 
					080af16d41 | ||
| 
						 | 
					d03a0b7083 | ||
| 
						 | 
					5ba21f5386 | ||
| 
						 | 
					10eeae5295 | ||
| 
						 | 
					a5bead15d0 | ||
| 
						 | 
					0de674adde | ||
| 
						 | 
					1db65965d0 | ||
| 
						 | 
					bbed17f631 | ||
| 
						 | 
					0af4a3a731 | ||
| 
						 | 
					49d503705a | ||
| 
						 | 
					c55dd7b8d9 | ||
| 
						 | 
					7ddcab3537 | ||
| 
						 | 
					040b46c345 | ||
| 
						 | 
					cd11ab6775 | ||
| 
						 | 
					a5d776f3b1 | ||
| 
						 | 
					e02caf341d | ||
| 
						 | 
					3c04caa67c | ||
| 
						 | 
					e76b564cbf | ||
| 
						 | 
					5b2de88c3d | ||
| 
						 | 
					82080b232f | ||
| 
						 | 
					666bc7af43 | ||
| 
						 | 
					dc077d5a5b | ||
| 
						 | 
					29c840c64a | ||
| 
						 | 
					65e0f89f33 | ||
| 
						 | 
					d3b9b36332 | ||
| 
						 | 
					90bbf29ea1 | ||
| 
						 | 
					57f73684e8 | ||
| 
						 | 
					7833cdebb2 | ||
| 
						 | 
					67a5ae2985 | ||
| 
						 | 
					f58c52d26b | ||
| 
						 | 
					41e7739461 | ||
| 
						 | 
					332152b677 | ||
| 
						 | 
					85b49fe1f0 | ||
| 
						 | 
					e7924532be | ||
| 
						 | 
					475d950ad6 | ||
| 
						 | 
					e6cfb29c6f | ||
| 
						 | 
					dee6e86db1 | ||
| 
						 | 
					72f088331f | ||
| 
						 | 
					bbf536d10e | ||
| 
						 | 
					149ac98297 | ||
| 
						 | 
					b90a2910c9 | ||
| 
						 | 
					c4da8a3a8d | ||
| 
						 | 
					3ca75583d2 | ||
| 
						 | 
					5f4607ae6f | ||
| 
						 | 
					d880c6873f | ||
| 
						 | 
					937649b2ed | ||
| 
						 | 
					78e912c886 | ||
| 
						 | 
					696c7d2cd1 | ||
| 
						 | 
					49c0cb026b | ||
| 
						 | 
					7091341b4b | ||
| 
						 | 
					8795ce6af3 | ||
| 
						 | 
					239b34d15a | ||
| 
						 | 
					729a5ad1a9 | ||
| 
						 | 
					8febc2476b | ||
| 
						 | 
					84b1317927 | ||
| 
						 | 
					bfb504e5db | ||
| 
						 | 
					9975deacfb | ||
| 
						 | 
					f77f071003 | ||
| 
						 | 
					4a8d55e83d | ||
| 
						 | 
					9a99f40e2a | ||
| 
						 | 
					428fa8035c | ||
| 
						 | 
					745f3dee17 | ||
| 
						 | 
					9907cc1875 | ||
| 
						 | 
					130cd780a2 | ||
| 
						 | 
					a808e5d1a5 | ||
| 
						 | 
					b926117e26 | ||
| 
						 | 
					fdf238accf | ||
| 
						 | 
					4e1c27e8a3 | ||
| 
						 | 
					a3e51a0ac5 | ||
| 
						 | 
					d27bb0d54f | ||
| 
						 | 
					f6a750f06b | ||
| 
						 | 
					c4e2f63e69 | ||
| 
						 | 
					675ffc8f42 | ||
| 
						 | 
					cdc4221175 | ||
| 
						 | 
					843f3363fd | ||
| 
						 | 
					17653a6374 | ||
| 
						 | 
					7d860533a0 | ||
| 
						 | 
					0c751b3ced | ||
| 
						 | 
					80a522ab06 | ||
| 
						 | 
					0718d76e00 | ||
| 
						 | 
					a4887c5358 | ||
| 
						 | 
					2ad5e122ff | ||
| 
						 | 
					832d874a0e | ||
| 
						 | 
					6a133d4dbd | ||
| 
						 | 
					d96dfa63c9 | ||
| 
						 | 
					d5c8c05426 | ||
| 
						 | 
					b6bb4647c7 | ||
| 
						 | 
					a81f06b743 | ||
| 
						 | 
					cb41a33546 | ||
| 
						 | 
					005068de9c | ||
| 
						 | 
					d8c7b1e070 | ||
| 
						 | 
					4835dfcc42 | ||
| 
						 | 
					15bceb09a2 | ||
| 
						 | 
					13e818abfd | ||
| 
						 | 
					9c5f6049b5 | ||
| 
						 | 
					2095b619a4 | ||
| 
						 | 
					df155cbc1b | ||
| 
						 | 
					3781729fd1 | ||
| 
						 | 
					d03c634ec0 | ||
| 
						 | 
					93c979c218 | ||
| 
						 | 
					90fef94583 | ||
| 
						 | 
					0b8eec2263 | ||
| 
						 | 
					9511af4e6d | ||
| 
						 | 
					7deaf1fd9f | ||
| 
						 | 
					43cc5bd520 | ||
| 
						 | 
					68b58aa520 | ||
| 
						 | 
					dbadd09af3 | ||
| 
						 | 
					fcbe2c754f | ||
| 
						 | 
					ad4580677b | ||
| 
						 | 
					01a6cc7d92 | ||
| 
						 | 
					95ce95a187 | ||
| 
						 | 
					ce8e6fbfb0 | ||
| 
						 | 
					a20eaed76b | ||
| 
						 | 
					419af72028 | ||
| 
						 | 
					8e499522f6 | ||
| 
						 | 
					84321b847e | ||
| 
						 | 
					c969a4d465 | ||
| 
						 | 
					0e12c850b6 | ||
| 
						 | 
					442835dd9b | ||
| 
						 | 
					b4ff99cb2e | ||
| 
						 | 
					aa0ecbc998 | ||
| 
						 | 
					cc412e93c0 | ||
| 
						 | 
					1d36fadbfa | ||
| 
						 | 
					56182bd87d | ||
| 
						 | 
					d290ff92b3 | ||
| 
						 | 
					298c5fd3b8 | ||
| 
						 | 
					e365c43781 | ||
| 
						 | 
					23a9f22ef4 | ||
| 
						 | 
					242f437237 | ||
| 
						 | 
					2eae8cffdb | ||
| 
						 | 
					68ab3d4f72 | ||
| 
						 | 
					1ea805aff0 | ||
| 
						 | 
					6ce45349dc | ||
| 
						 | 
					8f20cd9863 | ||
| 
						 | 
					4ed0e85680 | ||
| 
						 | 
					8ce9eb9955 | ||
| 
						 | 
					363330f3d1 | ||
| 
						 | 
					fbf9c5ddd6 | ||
| 
						 | 
					fbf2ee314c | ||
| 
						 | 
					c9f83fb419 | ||
| 
						 | 
					9737d91e16 | ||
| 
						 | 
					2f672ae970 | ||
| 
						 | 
					25339546c6 | ||
| 
						 | 
					912cad42ac | ||
| 
						 | 
					b3752c8d8f | ||
| 
						 | 
					e8a11333f2 | ||
| 
						 | 
					8bb6d09e6e | ||
| 
						 | 
					56bf955297 | ||
| 
						 | 
					ef6c08dfe8 | ||
| 
						 | 
					495c3e1cd7 | ||
| 
						 | 
					9a5fe3d744 | ||
| 
						 | 
					72607cae4d | ||
| 
						 | 
					4891cdb04d | ||
| 
						 | 
					d214881720 | ||
| 
						 | 
					e66c125dbf | ||
| 
						 | 
					9862bcfa05 | ||
| 
						 | 
					0ac5451bef | ||
| 
						 | 
					669479b815 | ||
| 
						 | 
					2721130566 | ||
| 
						 | 
					6e33ad943f | ||
| 
						 | 
					f4b7357802 | ||
| 
						 | 
					7a404a7e6a | ||
| 
						 | 
					5158700a79 | ||
| 
						 | 
					41d10c1b47 | ||
| 
						 | 
					3aaac91f07 | ||
| 
						 | 
					ea7ff080b8 | ||
| 
						 | 
					b5270958eb | ||
| 
						 | 
					b0a38eab8c | ||
| 
						 | 
					0a478e82ba | ||
| 
						 | 
					02d97a009c | ||
| 
						 | 
					33940cdb9b | ||
| 
						 | 
					7a63f9ee6b | ||
| 
						 | 
					89b41fff59 | ||
| 
						 | 
					7a15f71528 | ||
| 
						 | 
					cdce2f8761 | ||
| 
						 | 
					a2470bbe47 | ||
| 
						 | 
					dbdf1ddce0 | ||
| 
						 | 
					f767e42e6f | ||
| 
						 | 
					3051eb6369 | ||
| 
						 | 
					a41faddca3 | ||
| 
						 | 
					469038730e | ||
| 
						 | 
					b1fe5d2453 | ||
| 
						 | 
					f43e717da0 | ||
| 
						 | 
					95c8d4eef8 | ||
| 
						 | 
					747ca0ee82 | ||
| 
						 | 
					35a0184ec3 | ||
| 
						 | 
					96e1e5328c | ||
| 
						 | 
					7f98d6a0d8 | ||
| 
						 | 
					0194e25696 | ||
| 
						 | 
					189442e50f | ||
| 
						 | 
					d9bed070ec | ||
| 
						 | 
					a6193da8b5 | ||
| 
						 | 
					50d35b207d | ||
| 
						 | 
					19eb45bfa1 | ||
| 
						 | 
					01e0b24d9d | ||
| 
						 | 
					3c8024b16c | ||
| 
						 | 
					4c0ad0dd66 | ||
| 
						 | 
					501840086b | ||
| 
						 | 
					240b180857 | ||
| 
						 | 
					2bc5d0aaea | ||
| 
						 | 
					df66aa79b8 | ||
| 
						 | 
					6ea6a59ce3 | ||
| 
						 | 
					f3afa4d9d2 | ||
| 
						 | 
					e33d81eda1 | ||
| 
						 | 
					0fc4863dc4 | ||
| 
						 | 
					63471a5533 | ||
| 
						 | 
					050e90cb7e | ||
| 
						 | 
					b4643f2e45 | ||
| 
						 | 
					2d740a76b7 | ||
| 
						 | 
					0de9e3a02d | ||
| 
						 | 
					bedaa37e08 | ||
| 
						 | 
					ef9fa80b76 | ||
| 
						 | 
					dc2ea625ec | ||
| 
						 | 
					17df2c188a | ||
| 
						 | 
					6e050bccdf | ||
| 
						 | 
					8afbf3ce91 | ||
| 
						 | 
					9f57bf46a1 | ||
| 
						 | 
					4ce47da521 | ||
| 
						 | 
					4dc6382402 | ||
| 
						 | 
					cb8c1e32d9 | ||
| 
						 | 
					bf534be128 | ||
| 
						 | 
					a89c117612 | ||
| 
						 | 
					501dbcd76b | ||
| 
						 | 
					c0ceff1eec | ||
| 
						 | 
					6039ac5d4f | ||
| 
						 | 
					48e87f3c47 | ||
| 
						 | 
					c7866a1270 | ||
| 
						 | 
					4fadcd5964 | ||
| 
						 | 
					6480154d1b | ||
| 
						 | 
					9c064531b8 | ||
| 
						 | 
					bee64c2570 | ||
| 
						 | 
					5393321953 | ||
| 
						 | 
					b44669da87 | ||
| 
						 | 
					9d8bf1529a | ||
| 
						 | 
					a514825eaf | ||
| 
						 | 
					8abcacb517 | ||
| 
						 | 
					a3fc55e63d | ||
| 
						 | 
					a4c1d5bf98 | ||
| 
						 | 
					b16bc615fa | ||
| 
						 | 
					10ec8d6b97 | ||
| 
						 | 
					b04f0c9183 | ||
| 
						 | 
					45e9ebb0d4 | ||
| 
						 | 
					ac3e68a49f | ||
| 
						 | 
					b35ef1151a | ||
| 
						 | 
					ebd01bebd4 | ||
| 
						 | 
					7c7dd44805 | ||
| 
						 | 
					152ee60576 | ||
| 
						 | 
					9db568d726 | ||
| 
						 | 
					a34805d3dd | ||
| 
						 | 
					8105af7451 | ||
| 
						 | 
					88fd0eb3dd | ||
| 
						 | 
					0e406c7818 | ||
| 
						 | 
					778fe4a998 | ||
| 
						 | 
					17ffac57d4 | ||
| 
						 | 
					6cedf235ca | ||
| 
						 | 
					877b8c2338 | ||
| 
						 | 
					973f8508a8 | 
@@ -1,12 +1,7 @@
 | 
			
		||||
FROM elixir:1.16-otp-25
 | 
			
		||||
FROM elixir:1.17-otp-27
 | 
			
		||||
 | 
			
		||||
RUN apt update -yq
 | 
			
		||||
RUN apt install -yq curl gnupg mc inotify-tools
 | 
			
		||||
RUN apt install -yq curl gnupg
 | 
			
		||||
RUN apt --fix-broken install
 | 
			
		||||
RUN apt remove -y nodejs nodejs-doc
 | 
			
		||||
RUN curl -sL https://deb.nodesource.com/setup_18.x  | bash -
 | 
			
		||||
RUN apt install -y nodejs
 | 
			
		||||
RUN npm install --global yarn
 | 
			
		||||
 | 
			
		||||
RUN mix local.hex --force
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ version: "0.1"
 | 
			
		||||
 | 
			
		||||
services:
 | 
			
		||||
  db:
 | 
			
		||||
    image: postgres:14.3
 | 
			
		||||
    image: postgres:13-alpine
 | 
			
		||||
    restart: always
 | 
			
		||||
    environment:
 | 
			
		||||
      POSTGRES_USER: postgres
 | 
			
		||||
@@ -10,13 +10,13 @@ services:
 | 
			
		||||
    ports:
 | 
			
		||||
      - "5432:5432"
 | 
			
		||||
    volumes:
 | 
			
		||||
      - db:/var/lib/postgresql/data
 | 
			
		||||
      - db-new:/var/lib/postgresql/data
 | 
			
		||||
 | 
			
		||||
  wanderer:
 | 
			
		||||
    environment:
 | 
			
		||||
      PORT: 8000
 | 
			
		||||
      DB_HOST: db
 | 
			
		||||
      WEB_APP_URL: "http://localhost:4444"
 | 
			
		||||
      WEB_APP_URL: "http://localhost:8000"
 | 
			
		||||
      ERL_AFLAGS: "-kernel shell_history enabled"
 | 
			
		||||
    build:
 | 
			
		||||
      context: .
 | 
			
		||||
@@ -33,4 +33,4 @@ services:
 | 
			
		||||
 | 
			
		||||
volumes:
 | 
			
		||||
  elixir-artifacts: {}
 | 
			
		||||
  db: {}
 | 
			
		||||
  db-new: {}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										15
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
# These are supported funding model platforms
 | 
			
		||||
 | 
			
		||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
 | 
			
		||||
patreon: WandererLtd
 | 
			
		||||
open_collective: # Replace with a single Open Collective username
 | 
			
		||||
ko_fi: # Replace with a single Ko-fi username
 | 
			
		||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
 | 
			
		||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
 | 
			
		||||
liberapay: # Replace with a single Liberapay username
 | 
			
		||||
issuehunt: # Replace with a single IssueHunt username
 | 
			
		||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
 | 
			
		||||
polar: # Replace with a single Polar username
 | 
			
		||||
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
 | 
			
		||||
thanks_dev: # Replace with a single thanks.dev username
 | 
			
		||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
 | 
			
		||||
							
								
								
									
										39
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										39
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							@@ -58,6 +58,8 @@ jobs:
 | 
			
		||||
        otp: ["27"]
 | 
			
		||||
        elixir: ["1.17"]
 | 
			
		||||
        node-version: ["18.x"]
 | 
			
		||||
    outputs:
 | 
			
		||||
      commit_hash: ${{ steps.generate-changelog.outputs.commit_hash }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Prepare
 | 
			
		||||
        run: |
 | 
			
		||||
@@ -108,18 +110,17 @@ jobs:
 | 
			
		||||
        run: mix compile
 | 
			
		||||
 | 
			
		||||
      - name: Generate Changelog & Update Tag Version
 | 
			
		||||
        id: generate-changelog
 | 
			
		||||
        run: |
 | 
			
		||||
          git config --global user.name 'CI'
 | 
			
		||||
          git config --global user.email 'ci@users.noreply.github.com'
 | 
			
		||||
          mix git_ops.release --force-patch --yes
 | 
			
		||||
          yes | cp -rf CHANGELOG.md priv/changelog/CHANGELOG.md
 | 
			
		||||
          sed -i '1i%{title: "Change Log"}\n\n---\n' priv/changelog/CHANGELOG.md
 | 
			
		||||
          git push --follow-tags
 | 
			
		||||
          echo "commit_hash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
 | 
			
		||||
 | 
			
		||||
  docker:
 | 
			
		||||
    name: 🛠 Build Docker Images
 | 
			
		||||
    needs:
 | 
			
		||||
      - build
 | 
			
		||||
    needs: build
 | 
			
		||||
    runs-on: ubuntu-22.04
 | 
			
		||||
    permissions:
 | 
			
		||||
      checks: write
 | 
			
		||||
@@ -143,8 +144,14 @@ jobs:
 | 
			
		||||
      - name: ⬇️ Checkout repo
 | 
			
		||||
        uses: actions/checkout@v3
 | 
			
		||||
        with:
 | 
			
		||||
          ref: ${{ needs.build.outputs.commit_hash }}
 | 
			
		||||
          fetch-depth: 0
 | 
			
		||||
 | 
			
		||||
      - name: Prepare Changelog
 | 
			
		||||
        run: |
 | 
			
		||||
          yes | cp -rf CHANGELOG.md priv/changelog/CHANGELOG.md
 | 
			
		||||
          sed -i '1i%{title: "Change Log"}\n\n---\n' priv/changelog/CHANGELOG.md
 | 
			
		||||
 | 
			
		||||
      - name: Get Release Tag
 | 
			
		||||
        id: get-latest-tag
 | 
			
		||||
        uses: "WyriHaximus/github-action-get-previous-tag@v1"
 | 
			
		||||
@@ -186,6 +193,30 @@ jobs:
 | 
			
		||||
      - name: Image digest
 | 
			
		||||
        run: echo ${{ steps.build.outputs.digest }}
 | 
			
		||||
 | 
			
		||||
      - uses: markpatterson27/markdown-to-output@v1
 | 
			
		||||
        id: extract-changelog
 | 
			
		||||
        with:
 | 
			
		||||
          filepath: CHANGELOG.md
 | 
			
		||||
 | 
			
		||||
      - name: Get content
 | 
			
		||||
        uses: 2428392/gh-truncate-string-action@v1.3.0
 | 
			
		||||
        id: get-content
 | 
			
		||||
        with:
 | 
			
		||||
          stringToTruncate: |
 | 
			
		||||
            📣 Wanderer new release available 🎉
 | 
			
		||||
 | 
			
		||||
            **Version**: ${{ steps.get-latest-tag.outputs.tag }}
 | 
			
		||||
 | 
			
		||||
            ${{ steps.extract-changelog.outputs.body }}
 | 
			
		||||
          maxLength: 500
 | 
			
		||||
          truncationSymbol: "…"
 | 
			
		||||
 | 
			
		||||
      - name: Discord Webhook Action
 | 
			
		||||
        uses: tsickert/discord-webhook@v5.3.0
 | 
			
		||||
        with:
 | 
			
		||||
          webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
 | 
			
		||||
          content: ${{ steps.get-content.outputs.string }}
 | 
			
		||||
 | 
			
		||||
  create-release:
 | 
			
		||||
    name: 🏷 Create Release
 | 
			
		||||
    runs-on: ubuntu-22.04
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/release_actions.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/release_actions.yml
									
									
									
									
										vendored
									
									
								
							@@ -18,4 +18,4 @@ jobs:
 | 
			
		||||
          key: ${{ secrets.SSH_KEY }}
 | 
			
		||||
          port: ${{ secrets.PORT }}
 | 
			
		||||
          script: |
 | 
			
		||||
            /app/release/linux/deploy.sh ${{ github.event.release.tag_name }}
 | 
			
		||||
            /home/wanderer/app/deploy.sh ${{ github.event.release.tag_name }}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -12,6 +12,8 @@
 | 
			
		||||
 | 
			
		||||
# Ignore assets that are produced by build tools.
 | 
			
		||||
/priv/static/assets/
 | 
			
		||||
/priv/static/icons/
 | 
			
		||||
/priv/static/images/
 | 
			
		||||
/priv/static/*.js
 | 
			
		||||
/priv/static/*.css
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,3 @@
 | 
			
		||||
erlang 27.0.1
 | 
			
		||||
elixir 1.17-otp-27
 | 
			
		||||
erlang 26.2.5.5
 | 
			
		||||
elixir 1.17.3-otp-26
 | 
			
		||||
nodejs 18.0.0
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										416
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										416
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -2,17 +2,423 @@
 | 
			
		||||
 | 
			
		||||
<!-- changelog -->
 | 
			
		||||
 | 
			
		||||
## [v1.0.3](https://github.com/wanderer-industries/wanderer/compare/v1.0.2...v1.0.3) (2024-09-18)
 | 
			
		||||
## [v1.19.3](https://github.com/wanderer-industries/wanderer/compare/v1.19.2...v1.19.3) (2024-11-20)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## [v1.0.2](https://github.com/wanderer-industries/wanderer/compare/v1.0.1...v1.0.2) (2024-09-18)
 | 
			
		||||
### Bug Fixes:
 | 
			
		||||
 | 
			
		||||
* Core: Fix adding systems on splash (#71)
 | 
			
		||||
 | 
			
		||||
* Core: Fix adding systems on splash
 | 
			
		||||
 | 
			
		||||
## [v1.19.2](https://github.com/wanderer-industries/wanderer/compare/v1.19.1...v1.19.2) (2024-11-19)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## [v1.19.1](https://github.com/wanderer-industries/wanderer/compare/v1.19.0...v1.19.1) (2024-11-19)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## [v1.19.0](https://github.com/wanderer-industries/wanderer/compare/v1.18.1...v1.19.0) (2024-11-19)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Features:
 | 
			
		||||
 | 
			
		||||
* Signatures: Add user setting to show Inserted time in a separate column
 | 
			
		||||
 | 
			
		||||
## [v1.18.1](https://github.com/wanderer-industries/wanderer/compare/v1.18.0...v1.18.1) (2024-11-17)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## [v1.18.0](https://github.com/wanderer-industries/wanderer/compare/v1.17.0...v1.18.0) (2024-11-16)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Features:
 | 
			
		||||
 | 
			
		||||
* Map: a lot of design issues
 | 
			
		||||
 | 
			
		||||
## [v1.17.0](https://github.com/wanderer-industries/wanderer/compare/v1.16.1...v1.17.0) (2024-11-15)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Features:
 | 
			
		||||
 | 
			
		||||
* Signatures: Add user setting to show Description in a separate column
 | 
			
		||||
 | 
			
		||||
## [v1.16.1](https://github.com/wanderer-industries/wanderer/compare/v1.16.0...v1.16.1) (2024-11-15)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes:
 | 
			
		||||
 | 
			
		||||
* Signatures: Fix signature stored filters
 | 
			
		||||
 | 
			
		||||
## [v1.16.0](https://github.com/wanderer-industries/wanderer/compare/v1.15.5...v1.16.0) (2024-11-15)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Features:
 | 
			
		||||
 | 
			
		||||
* Signatures: Add additional filters support to signature list, show description icon
 | 
			
		||||
 | 
			
		||||
## [v1.15.5](https://github.com/wanderer-industries/wanderer/compare/v1.15.4...v1.15.5) (2024-11-14)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## [v1.15.4](https://github.com/wanderer-industries/wanderer/compare/v1.15.3...v1.15.4) (2024-11-14)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes:
 | 
			
		||||
 | 
			
		||||
* Core: Untracked characters still tracked on map (#63)
 | 
			
		||||
 | 
			
		||||
## [v1.15.3](https://github.com/wanderer-industries/wanderer/compare/v1.15.2...v1.15.3) (2024-11-13)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## [v1.15.2](https://github.com/wanderer-industries/wanderer/compare/v1.15.1...v1.15.2) (2024-11-07)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## [v1.15.1](https://github.com/wanderer-industries/wanderer/compare/v1.15.0...v1.15.1) (2024-11-07)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes:
 | 
			
		||||
 | 
			
		||||
* Dev: Update .devcontainer instructions
 | 
			
		||||
 | 
			
		||||
## [v1.15.0](https://github.com/wanderer-industries/wanderer/compare/v1.14.1...v1.15.0) (2024-11-07)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Features:
 | 
			
		||||
 | 
			
		||||
* Connections: Add connection mark EOL time (#56)
 | 
			
		||||
 | 
			
		||||
## [v1.14.1](https://github.com/wanderer-industries/wanderer/compare/v1.14.0...v1.14.1) (2024-11-06)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes:
 | 
			
		||||
 | 
			
		||||
* Core: Fix character tracking permissions
 | 
			
		||||
 | 
			
		||||
## [v1.14.0](https://github.com/wanderer-industries/wanderer/compare/v1.13.12...v1.14.0) (2024-11-05)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Features:
 | 
			
		||||
 | 
			
		||||
* ACL: Add an ability to assign member role without DnD
 | 
			
		||||
 | 
			
		||||
## [v1.13.12](https://github.com/wanderer-industries/wanderer/compare/v1.13.11...v1.13.12) (2024-11-04)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes:
 | 
			
		||||
 | 
			
		||||
* Map: Fix system revert issues
 | 
			
		||||
 | 
			
		||||
## [v1.13.11](https://github.com/wanderer-industries/wanderer/compare/v1.13.10...v1.13.11) (2024-11-02)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes:
 | 
			
		||||
 | 
			
		||||
* Map: Fix system revert issues
 | 
			
		||||
 | 
			
		||||
## [v1.13.10](https://github.com/wanderer-industries/wanderer/compare/v1.13.9...v1.13.10) (2024-11-01)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes:
 | 
			
		||||
 | 
			
		||||
* Map: Fix system revert issues
 | 
			
		||||
 | 
			
		||||
## [v1.13.9](https://github.com/wanderer-industries/wanderer/compare/v1.13.8...v1.13.9) (2024-11-01)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## [v1.13.8](https://github.com/wanderer-industries/wanderer/compare/v1.13.7...v1.13.8) (2024-10-28)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## [v1.13.0](https://github.com/wanderer-industries/wanderer/compare/v1.12.11...v1.13.0) (2024-10-28)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Features:
 | 
			
		||||
 | 
			
		||||
* Core: Use ESI /characters/affiliation API
 | 
			
		||||
 | 
			
		||||
## [v1.12.4](https://github.com/wanderer-industries/wanderer/compare/v1.12.3...v1.12.4) (2024-10-21)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes:
 | 
			
		||||
 | 
			
		||||
* Map: Fix systems cleanup
 | 
			
		||||
 | 
			
		||||
## [v1.12.3](https://github.com/wanderer-industries/wanderer/compare/v1.12.2...v1.12.3) (2024-10-18)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes:
 | 
			
		||||
 | 
			
		||||
* Map: Fix regression issues
 | 
			
		||||
 | 
			
		||||
## [v1.12.1](https://github.com/wanderer-industries/wanderer/compare/v1.12.0...v1.12.1) (2024-10-16)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes:
 | 
			
		||||
 | 
			
		||||
* Map: Fix system add error after map page refresh
 | 
			
		||||
 | 
			
		||||
## [v1.12.0](https://github.com/wanderer-industries/wanderer/compare/v1.11.5...v1.12.0) (2024-10-16)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Features:
 | 
			
		||||
 | 
			
		||||
* Map: Prettify user settings
 | 
			
		||||
 | 
			
		||||
## [v1.11.0](https://github.com/wanderer-industries/wanderer/compare/v1.10.0...v1.11.0) (2024-10-14)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Features:
 | 
			
		||||
 | 
			
		||||
* Map: Add map level option to store custom labels
 | 
			
		||||
 | 
			
		||||
## [v1.10.0](https://github.com/wanderer-industries/wanderer/compare/v1.9.0...v1.10.0) (2024-10-13)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Features:
 | 
			
		||||
 | 
			
		||||
* Map: Link signature on splash
 | 
			
		||||
 | 
			
		||||
## [v1.5.0](https://github.com/wanderer-industries/wanderer/compare/v1.4.0...v1.5.0) (2024-10-11)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Features:
 | 
			
		||||
 | 
			
		||||
* Map: Follow Character on Map and auto select their current system
 | 
			
		||||
 | 
			
		||||
## [v1.3.6](https://github.com/wanderer-industries/wanderer/compare/v1.3.5...v1.3.6) (2024-10-09)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes:
 | 
			
		||||
 | 
			
		||||
* Signatures: Signatures update fixes
 | 
			
		||||
 | 
			
		||||
## [v1.3.0](https://github.com/wanderer-industries/wanderer/compare/v1.2.10...v1.3.0) (2024-10-07)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Features:
 | 
			
		||||
 | 
			
		||||
* Map: Fix default sort
 | 
			
		||||
 | 
			
		||||
* Map: Remove resizible and fix styles of column sorting
 | 
			
		||||
 | 
			
		||||
* Map: Revision of sorting from also adding ability to sort all columns
 | 
			
		||||
 | 
			
		||||
## [v1.2.6](https://github.com/wanderer-industries/wanderer/compare/v1.2.5...v1.2.6) (2024-10-05)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes:
 | 
			
		||||
 | 
			
		||||
* Core: Stability & performance improvements
 | 
			
		||||
 | 
			
		||||
## [v1.2.5](https://github.com/wanderer-industries/wanderer/compare/v1.2.4...v1.2.5) (2024-10-04)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes:
 | 
			
		||||
 | 
			
		||||
* Core: Add system "true security" correction
 | 
			
		||||
 | 
			
		||||
## [v1.2.4](https://github.com/wanderer-industries/wanderer/compare/v1.2.3...v1.2.4) (2024-10-03)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes:
 | 
			
		||||
 | 
			
		||||
* Map: Remove duplicate connections
 | 
			
		||||
 | 
			
		||||
## [v1.2.3](https://github.com/wanderer-industries/wanderer/compare/v1.2.2...v1.2.3) (2024-10-02)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes:
 | 
			
		||||
 | 
			
		||||
* Map: Fix map loading after select a different map.
 | 
			
		||||
 | 
			
		||||
## [v1.2.1](https://github.com/wanderer-industries/wanderer/compare/v1.2.0...v1.2.1) (2024-10-02)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes:
 | 
			
		||||
 | 
			
		||||
* ACL: Fix allowing to save map/access list with empty owner set
 | 
			
		||||
 | 
			
		||||
## [v1.2.0](https://github.com/wanderer-industries/wanderer/compare/v1.1.0...v1.2.0) (2024-09-29)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Features:
 | 
			
		||||
 | 
			
		||||
* Map: Add ability to open jump planner from routes
 | 
			
		||||
 | 
			
		||||
## [v1.1.0](https://github.com/wanderer-industries/wanderer/compare/v1.0.23...v1.1.0) (2024-09-29)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Features:
 | 
			
		||||
 | 
			
		||||
* Map: Add highlighting for imperial space systems depends on faction
 | 
			
		||||
 | 
			
		||||
## [v1.0.23](https://github.com/wanderer-industries/wanderer/compare/v1.0.22...v1.0.23) (2024-09-25)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes:
 | 
			
		||||
 | 
			
		||||
* Map: Main map doesn't load back after refreshing/switching pages
 | 
			
		||||
 | 
			
		||||
## [v1.0.22](https://github.com/wanderer-industries/wanderer/compare/v1.0.21...v1.0.22) (2024-09-25)
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* Map: Main map doesn't load back after refreshing/switching pages
 | 
			
		||||
 | 
			
		||||
## [v1.0.21](https://github.com/wanderer-industries/wanderer/compare/v1.0.20...v1.0.21) (2024-09-24)
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* Map: Main map doesn't load back after refreshing/switching pages
 | 
			
		||||
 | 
			
		||||
## [v1.0.20](https://github.com/wanderer-industries/wanderer/compare/v1.0.19...v1.0.20) (2024-09-23)
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* core: Small fixes & improvements
 | 
			
		||||
 | 
			
		||||
## [v1.0.19](https://github.com/wanderer-industries/wanderer/compare/v1.0.18...v1.0.19) (2024-09-23)
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* ACL: Fix adding empty members list
 | 
			
		||||
 | 
			
		||||
## [v1.0.18](https://github.com/wanderer-industries/wanderer/compare/v1.0.17...v1.0.18) (2024-09-22)
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* ACL: Cant delete ACL list after map deletion #5
 | 
			
		||||
 | 
			
		||||
## [v1.0.16](https://github.com/wanderer-industries/wanderer/compare/v1.0.15...v1.0.16) (2024-09-21)
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* Map: commented console log
 | 
			
		||||
 | 
			
		||||
* Map: add console log for check sys loading
 | 
			
		||||
 | 
			
		||||
* Map: add key for cache changes detecting
 | 
			
		||||
 | 
			
		||||
## [v1.0.15](https://github.com/wanderer-industries/wanderer/compare/v1.0.14...v1.0.15) (2024-09-21)
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* map: Show a proper user notification if map was deleted/archived
 | 
			
		||||
 | 
			
		||||
## [v1.0.14](https://github.com/wanderer-industries/wanderer/compare/v1.0.13...v1.0.14) (2024-09-21)
 | 
			
		||||
 | 
			
		||||
## [v1.0.13](https://github.com/wanderer-industries/wanderer/compare/v1.0.12...v1.0.13) (2024-09-21)
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* tracking: Ensure user has at least one character tracked to work with map
 | 
			
		||||
 | 
			
		||||
## [v1.0.12](https://github.com/wanderer-industries/wanderer/compare/v1.0.11...v1.0.12) (2024-09-20)
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* audit: Hide character for non-character map activities
 | 
			
		||||
 | 
			
		||||
## [v1.0.11](https://github.com/wanderer-industries/wanderer/compare/v1.0.10...v1.0.11) (2024-09-20)
 | 
			
		||||
 | 
			
		||||
## [v1.0.10](https://github.com/wanderer-industries/wanderer/compare/v1.0.9...v1.0.10) (2024-09-19)
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* signatures: Fix update signatures error if no character tracked on map
 | 
			
		||||
 | 
			
		||||
## [v1.0.9](https://github.com/wanderer-industries/wanderer/compare/v1.0.8...v1.0.9) (2024-09-19)
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* core: Fix system add error if it's already added on map
 | 
			
		||||
 | 
			
		||||
## [v1.0.8](https://github.com/wanderer-industries/wanderer/compare/v1.0.7...v1.0.8) (2024-09-19)
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* docker: Fix DB connection in docker-compose internal network
 | 
			
		||||
 | 
			
		||||
## [v1.0.4](https://github.com/wanderer-industries/wanderer/compare/v1.0.3...v1.0.4) (2024-09-18)
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* core: skip search results for failed character info request
 | 
			
		||||
 | 
			
		||||
## [v1.0.1](https://github.com/wanderer-industries/wanderer/compare/v1.0.0...v1.0.1) (2024-09-18)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								README.md
									
									
									
									
									
								
							@@ -20,11 +20,11 @@ Interested to learn more? [Check more on our website](https://wanderer.ltd/news)
 | 
			
		||||
 | 
			
		||||
Wanderer is open source project and we have a free as in beer and self-hosted solution called [Wanderer Community Edition (CE)](https://wanderer.ltd/news/community-edition). Here are the differences between Wanderer and Wanderer CE:
 | 
			
		||||
 | 
			
		||||
|  | Wanderer Cloud  | Wanderer Community Edition |
 | 
			
		||||
| ------------- | ------------- | ------------- |
 | 
			
		||||
| **Infrastructure management** | Easy and convenient. It takes 2 minutes to register your character and create a map. We manage everything so you don’t have to worry about anything and can focus on gameplay. | You do it all yourself. You need to get a server and you need to manage your infrastructure. You are responsible for installation, maintenance, upgrades, server capacity, uptime, backup, security, stability, consistency, loading time and so on.|
 | 
			
		||||
| **Release schedule** | Continuously developed and improved with new features and updates multiple times per week. | Latest features and improvements won't be immediately available.|
 | 
			
		||||
| **Server location** | All visitor data is exclusively processed on EU-owned cloud infrastructure. We keep your site data on a secure, encrypted and green energy powered server in Germany. This ensures that your site data is protected by the strict European Union data privacy laws and ensures compliance with GDPR. Your website data never leaves the EU. | You have full control and can host your instance on any server in any country that you wish. Host it on a server in your basement or host it with any cloud provider wherever you want, even those that are not GDPR compliant.|
 | 
			
		||||
|                               | Wanderer Cloud                                                                                                                                                                                                                                                                                                                              | Wanderer Community Edition                                                                                                                                                                                                                           |
 | 
			
		||||
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
 | 
			
		||||
| **Infrastructure management** | Easy and convenient. It takes 2 minutes to register your character and create a map. We manage everything so you don’t have to worry about anything and can focus on gameplay.                                                                                                                                                              | You do it all yourself. You need to get a server and you need to manage your infrastructure. You are responsible for installation, maintenance, upgrades, server capacity, uptime, backup, security, stability, consistency, loading time and so on. |
 | 
			
		||||
| **Release schedule**          | Continuously developed and improved with new features and updates multiple times per week.                                                                                                                                                                                                                                                  | Latest features and improvements won't be immediately available.                                                                                                                                                                                     |
 | 
			
		||||
| **Server location**           | All visitor data is exclusively processed on EU-owned cloud infrastructure. We keep your site data on a secure, encrypted and green energy powered server in Germany. This ensures that your site data is protected by the strict European Union data privacy laws and ensures compliance with GDPR. Your website data never leaves the EU. | You have full control and can host your instance on any server in any country that you wish. Host it on a server in your basement or host it with any cloud provider wherever you want, even those that are not GDPR compliant.                      |
 | 
			
		||||
 | 
			
		||||
Interested in self-hosting Wanderer CE on your server? Take a look at our [Wanderer CE installation instructions](https://github.com/wanderer-industries/community-edition/).
 | 
			
		||||
 | 
			
		||||
@@ -54,7 +54,13 @@ Now you can visit [`localhost:8000`](http://localhost:8000) from your browser.
 | 
			
		||||
#### Using .devcontainer
 | 
			
		||||
 | 
			
		||||
- Run devcontainer
 | 
			
		||||
- See how to start server in #setup section
 | 
			
		||||
- Install additional dependencies inside Dev container
 | 
			
		||||
- `root@0d0a785313b6:/app# apt update`
 | 
			
		||||
- `root@0d0a785313b6:/app# curl -sL https://deb.nodesource.com/setup_18.x  | bash -`
 | 
			
		||||
- `root@0d0a785313b6:/app# apt-get install nodejs inotify-tools -y`
 | 
			
		||||
- `root@0d0a785313b6:/app# mix setup`
 | 
			
		||||
 | 
			
		||||
- See how to run server in #Run section
 | 
			
		||||
 | 
			
		||||
#### Using nix flakes
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,12 @@ body {
 | 
			
		||||
  font-weight: 500;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#bg-canvas {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  width: 100vw;
 | 
			
		||||
  height: 100vh;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.ccp-font {
 | 
			
		||||
  font-family: 'Shentox', 'Rogan', sans-serif !important;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,11 +15,10 @@ 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([mapRef, providerRef]);
 | 
			
		||||
  const mapperHandlerRefs = useRef([providerRef]);
 | 
			
		||||
 | 
			
		||||
  const { handleCommand, handleMapEvent, handleMapEvents } = useMapperHandlers(mapperHandlerRefs.current, hooksRef);
 | 
			
		||||
 | 
			
		||||
@@ -41,7 +40,7 @@ export default function MapRoot({ hooks }) {
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <PrimeReactProvider>
 | 
			
		||||
      <MapRootProvider fwdRef={providerRef} outCommand={handleCommand} mapRef={mapRef}>
 | 
			
		||||
      <MapRootProvider fwdRef={providerRef} outCommand={handleCommand}>
 | 
			
		||||
        <ErrorBoundary FallbackComponent={ErrorFallback} onError={logError}>
 | 
			
		||||
          <ReactFlowProvider>
 | 
			
		||||
            <MapRootContent />
 | 
			
		||||
 
 | 
			
		||||
@@ -67,3 +67,44 @@
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.p-sortable-column {
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
  font-weight: bold;
 | 
			
		||||
  padding: 3px 4px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.p-selectable-row td {
 | 
			
		||||
  padding: 4px 4px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.p-sortable-column > .p-column-header-content > span:last-child {
 | 
			
		||||
  transform: scale(0.7);
 | 
			
		||||
 | 
			
		||||
  & > svg {
 | 
			
		||||
    margin-left: 4px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.p-dropdown-label, .p-inputtext {
 | 
			
		||||
  padding: 0.25rem 0.75rem;
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.p-dropdown-item {
 | 
			
		||||
  padding: 0.25rem 0.5rem;
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.p-dropdown-item-group {
 | 
			
		||||
  padding: 0.25rem 0.75rem;
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.p-dropdown-trigger {
 | 
			
		||||
  width: 14px;
 | 
			
		||||
  margin: 0 12px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.p-dropdown-empty-message {
 | 
			
		||||
  padding: 0.25rem 0.5rem;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/* Основной класс диалога */
 | 
			
		||||
.p-dialog {
 | 
			
		||||
body .p-dialog {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: column;
 | 
			
		||||
  //position: absolute;
 | 
			
		||||
@@ -7,11 +7,26 @@
 | 
			
		||||
  left: 0;
 | 
			
		||||
  //visibility: hidden;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  border-radius: 6px;
 | 
			
		||||
  border-radius: 2px;
 | 
			
		||||
  box-shadow: 0 2px 10px 0 rgba(0,0,0,0.2);
 | 
			
		||||
  transition: box-shadow 0.3s;
 | 
			
		||||
  background: #fff;
 | 
			
		||||
  z-index: 1000;
 | 
			
		||||
  border: 1px solid #212121;
 | 
			
		||||
  background: var(--surface-h);
 | 
			
		||||
  color: var(--text-color);
 | 
			
		||||
 | 
			
		||||
  .p-dialog-header {
 | 
			
		||||
    background: #171717 !important;
 | 
			
		||||
    color: var(--text-color);
 | 
			
		||||
 | 
			
		||||
    .p-dialog-header-icon:focus-visible {
 | 
			
		||||
      box-shadow: none !important;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .p-dialog-footer {
 | 
			
		||||
    border-top: 1px solid var(--surface-d);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Стиль видимого диалога */
 | 
			
		||||
@@ -45,12 +60,12 @@
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
  padding: 1rem;
 | 
			
		||||
  background: #f4f4f4;
 | 
			
		||||
  border-bottom: 1px solid #ddd;
 | 
			
		||||
  //border-bottom: 1px solid #ddd;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Содержимое диалога */
 | 
			
		||||
.p-dialog-content {
 | 
			
		||||
  padding: 1rem;
 | 
			
		||||
  padding: 0.5rem;
 | 
			
		||||
  overflow-y: auto;
 | 
			
		||||
  flex: 1;
 | 
			
		||||
}
 | 
			
		||||
@@ -78,23 +93,3 @@
 | 
			
		||||
.p-dialog-header-close .pi {
 | 
			
		||||
  font-size: 1.25rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Тема Saga Blue (пример) */
 | 
			
		||||
body .p-dialog {
 | 
			
		||||
  background: var(--surface-a);
 | 
			
		||||
  color: var(--text-color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
body .p-dialog .p-dialog-header,
 | 
			
		||||
body .p-dialog .p-dialog-footer {
 | 
			
		||||
  background: var(--surface-b);
 | 
			
		||||
  color: var(--text-color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
body .p-dialog .p-dialog-header {
 | 
			
		||||
  border-bottom: 1px solid var(--surface-d);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
body .p-dialog .p-dialog-footer {
 | 
			
		||||
  border-top: 1px solid var(--surface-d);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@
 | 
			
		||||
  --surface-d: #3f4b5b;
 | 
			
		||||
  --surface-e: #2a323d;
 | 
			
		||||
  --surface-f: #2a323d;
 | 
			
		||||
  --surface-h: #171717;
 | 
			
		||||
  --text-color: rgba(255, 255, 255, 0.87);
 | 
			
		||||
  --text-color-secondary: rgba(255, 255, 255, 0.6);
 | 
			
		||||
  --primary-color: #8dd0ff;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +1,19 @@
 | 
			
		||||
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) => {
 | 
			
		||||
      mapRef.current?.command(Commands.selectSystem, character?.location?.solar_system_id?.toString());
 | 
			
		||||
    },
 | 
			
		||||
    [mapRef],
 | 
			
		||||
  );
 | 
			
		||||
  const handleSelect = useCallback((character: CharacterTypeRaw) => {
 | 
			
		||||
    emitMapEvent({
 | 
			
		||||
      name: Commands.centerSystem,
 | 
			
		||||
      data: character?.location?.solar_system_id?.toString(),
 | 
			
		||||
    });
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const items = data.map(character => (
 | 
			
		||||
    <li
 | 
			
		||||
 
 | 
			
		||||
@@ -46,7 +46,7 @@ export const useLabelsMenu = (
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // const labels = getLabels(system.labels);
 | 
			
		||||
    const hasLabels = labels.list.length > 0;
 | 
			
		||||
    const hasLabels = labels?.list?.length > 0;
 | 
			
		||||
    const statusList = hasLabels ? LABELS_ORDER : LABELS_ORDER.slice(1);
 | 
			
		||||
 | 
			
		||||
    return [
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@ export const useTagMenu = (
 | 
			
		||||
  ref.current = { onSystemTag, systems, systemId };
 | 
			
		||||
 | 
			
		||||
  return useCallback(() => {
 | 
			
		||||
    const { onSystemTag, systemId , systems} = ref.current;
 | 
			
		||||
    const { onSystemTag, systemId, systems } = ref.current;
 | 
			
		||||
    const system = systemId ? getSystemById(systems, systemId) : undefined;
 | 
			
		||||
 | 
			
		||||
    const isSelectedLetters = AVAILABLE_LETTERS.includes(system?.tag ?? '');
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ import { OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.
 | 
			
		||||
import { SolarSystemRawType } from '@/hooks/Mapper/types';
 | 
			
		||||
import { WaypointSetContextHandler } from '@/hooks/Mapper/components/contexts/types.ts';
 | 
			
		||||
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
 | 
			
		||||
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
 | 
			
		||||
 | 
			
		||||
interface UseContextMenuSystemHandlersProps {
 | 
			
		||||
  hubs: string[];
 | 
			
		||||
@@ -16,8 +17,10 @@ export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseC
 | 
			
		||||
 | 
			
		||||
  const [system, setSystem] = useState<string>();
 | 
			
		||||
 | 
			
		||||
  const ref = useRef({ hubs, system, systems, outCommand });
 | 
			
		||||
  ref.current = { hubs, system, systems, outCommand };
 | 
			
		||||
  const { deleteSystems } = useDeleteSystems();
 | 
			
		||||
 | 
			
		||||
  const ref = useRef({ hubs, system, systems, outCommand, deleteSystems });
 | 
			
		||||
  ref.current = { hubs, system, systems, outCommand, deleteSystems };
 | 
			
		||||
 | 
			
		||||
  const open = useCallback((ev: any, systemId: string) => {
 | 
			
		||||
    setSystem(systemId);
 | 
			
		||||
@@ -27,12 +30,12 @@ export const useContextMenuSystemHandlers = ({ systems, hubs, outCommand }: UseC
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const onDeleteSystem = useCallback(() => {
 | 
			
		||||
    const { system, outCommand } = ref.current;
 | 
			
		||||
    const { system, deleteSystems } = ref.current;
 | 
			
		||||
    if (!system) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    outCommand({ type: OutCommand.deleteSystems, data: [system] });
 | 
			
		||||
    deleteSystems([system]);
 | 
			
		||||
    setSystem(undefined);
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,17 +8,21 @@ import { getSystemById } from '@/hooks/Mapper/helpers';
 | 
			
		||||
import { useWaypointMenu } from '@/hooks/Mapper/components/contexts/hooks';
 | 
			
		||||
import { WaypointSetContextHandler } from '@/hooks/Mapper/components/contexts/types.ts';
 | 
			
		||||
import { FastSystemActions } from '@/hooks/Mapper/components/contexts/components';
 | 
			
		||||
import { useJumpPlannerMenu } from '@/hooks/Mapper/components/contexts/hooks';
 | 
			
		||||
import { Route } from '@/hooks/Mapper/types/routes.ts';
 | 
			
		||||
 | 
			
		||||
export interface ContextMenuSystemInfoProps {
 | 
			
		||||
  systemStatics: Map<number, SolarSystemStaticInfoRaw>;
 | 
			
		||||
  hubs: string[];
 | 
			
		||||
  contextMenuRef: RefObject<ContextMenu>;
 | 
			
		||||
  systemId: string | undefined;
 | 
			
		||||
  systemIdFrom?: string | undefined;
 | 
			
		||||
  systems: SolarSystemRawType[];
 | 
			
		||||
  onOpenSettings(): void;
 | 
			
		||||
  onHubToggle(): void;
 | 
			
		||||
  onAddSystem(): void;
 | 
			
		||||
  onWaypointSet: WaypointSetContextHandler;
 | 
			
		||||
  routes: Route[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const ContextMenuSystemInfo: React.FC<ContextMenuSystemInfoProps> = ({
 | 
			
		||||
@@ -30,9 +34,12 @@ export const ContextMenuSystemInfo: React.FC<ContextMenuSystemInfoProps> = ({
 | 
			
		||||
  onAddSystem,
 | 
			
		||||
  onWaypointSet,
 | 
			
		||||
  systemId,
 | 
			
		||||
  systemIdFrom,
 | 
			
		||||
  hubs,
 | 
			
		||||
  routes,
 | 
			
		||||
}) => {
 | 
			
		||||
  const getWaypointMenu = useWaypointMenu(onWaypointSet);
 | 
			
		||||
  const getJumpPlannerMenu = useJumpPlannerMenu(systems, systemIdFrom);
 | 
			
		||||
 | 
			
		||||
  const items: MenuItem[] = useMemo(() => {
 | 
			
		||||
    const system = systemId ? systemStatics.get(parseInt(systemId)) : undefined;
 | 
			
		||||
@@ -55,7 +62,9 @@ export const ContextMenuSystemInfo: React.FC<ContextMenuSystemInfoProps> = ({
 | 
			
		||||
          );
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      { separator: true },
 | 
			
		||||
      ...getJumpPlannerMenu(system, routes),
 | 
			
		||||
      ...getWaypointMenu(systemId, system.system_class),
 | 
			
		||||
      {
 | 
			
		||||
        label: !hubs.includes(systemId) ? 'Add in Routes' : 'Remove from Routes',
 | 
			
		||||
@@ -72,7 +81,17 @@ export const ContextMenuSystemInfo: React.FC<ContextMenuSystemInfoProps> = ({
 | 
			
		||||
          ]
 | 
			
		||||
        : []),
 | 
			
		||||
    ];
 | 
			
		||||
  }, [systemId, systemStatics, systems, getWaypointMenu, hubs, onHubToggle, onAddSystem, onOpenSettings]);
 | 
			
		||||
  }, [
 | 
			
		||||
    systemId,
 | 
			
		||||
    systemStatics,
 | 
			
		||||
    systems,
 | 
			
		||||
    getJumpPlannerMenu,
 | 
			
		||||
    getWaypointMenu,
 | 
			
		||||
    hubs,
 | 
			
		||||
    onHubToggle,
 | 
			
		||||
    onAddSystem,
 | 
			
		||||
    onOpenSettings,
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,30 +1,36 @@
 | 
			
		||||
import { RefObject, useCallback, useRef, useState } from 'react';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import { useCallback, useRef, useState } from 'react';
 | 
			
		||||
import { ContextMenu } from 'primereact/contextmenu';
 | 
			
		||||
import { Commands, MapHandlers, OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts';
 | 
			
		||||
import { Commands, 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, mapRef }: UseContextMenuSystemHandlersProps) => {
 | 
			
		||||
export const useContextMenuSystemInfoHandlers = ({ hubs, outCommand }: UseContextMenuSystemHandlersProps) => {
 | 
			
		||||
  const contextMenuRef = useRef<ContextMenu | null>(null);
 | 
			
		||||
 | 
			
		||||
  const [system, setSystem] = useState<string>();
 | 
			
		||||
  const routeRef = useRef<(SolarSystemStaticInfoRaw | undefined)[]>([]);
 | 
			
		||||
 | 
			
		||||
  const ref = useRef({ hubs, system, outCommand, mapRef });
 | 
			
		||||
  ref.current = { hubs, system, outCommand, mapRef };
 | 
			
		||||
  const ref = useRef({ hubs, system, outCommand });
 | 
			
		||||
  ref.current = { hubs, system, outCommand };
 | 
			
		||||
 | 
			
		||||
  const open = useCallback((ev: React.SyntheticEvent, systemId: string) => {
 | 
			
		||||
    setSystem(systemId);
 | 
			
		||||
    ev.preventDefault();
 | 
			
		||||
    ctxManager.next('ctxSysInfo', contextMenuRef.current);
 | 
			
		||||
    contextMenuRef.current?.show(ev);
 | 
			
		||||
  }, []);
 | 
			
		||||
  const open = useCallback(
 | 
			
		||||
    (ev: React.SyntheticEvent, systemId: string, route: (SolarSystemStaticInfoRaw | undefined)[]) => {
 | 
			
		||||
      setSystem(systemId);
 | 
			
		||||
      routeRef.current = route;
 | 
			
		||||
      ev.preventDefault();
 | 
			
		||||
      ctxManager.next('ctxSysInfo', contextMenuRef.current);
 | 
			
		||||
      contextMenuRef.current?.show(ev);
 | 
			
		||||
    },
 | 
			
		||||
    [],
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const onHubToggle = useCallback(() => {
 | 
			
		||||
    const { hubs, system, outCommand } = ref.current;
 | 
			
		||||
@@ -42,19 +48,23 @@ export const useContextMenuSystemInfoHandlers = ({ hubs, outCommand, mapRef }: U
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const onAddSystem = useCallback(() => {
 | 
			
		||||
    const { system, outCommand, mapRef } = ref.current;
 | 
			
		||||
    if (!system) {
 | 
			
		||||
    const { system: solarSystemId, outCommand } = ref.current;
 | 
			
		||||
    if (!solarSystemId) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    outCommand({
 | 
			
		||||
      type: OutCommand.addSystem,
 | 
			
		||||
      data: {
 | 
			
		||||
        system_id: system,
 | 
			
		||||
        system_id: solarSystemId,
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
      mapRef.current?.command(Commands.selectSystem, system);
 | 
			
		||||
      emitMapEvent({
 | 
			
		||||
        name: Commands.centerSystem,
 | 
			
		||||
        data: solarSystemId,
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      setSystem(undefined);
 | 
			
		||||
    }, 200);
 | 
			
		||||
  }, []);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,17 @@
 | 
			
		||||
import { Node } from 'reactflow';
 | 
			
		||||
import { useRef, useState } from 'react';
 | 
			
		||||
import { useCallback, useRef, useState } from 'react';
 | 
			
		||||
import { ContextMenu } from 'primereact/contextmenu';
 | 
			
		||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
 | 
			
		||||
import { SolarSystemRawType } from '@/hooks/Mapper/types';
 | 
			
		||||
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
 | 
			
		||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
 | 
			
		||||
import { NodeSelectionMouseHandler } from '@/hooks/Mapper/components/contexts/types.ts';
 | 
			
		||||
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
 | 
			
		||||
 | 
			
		||||
export const useContextMenuSystemMultipleHandlers = () => {
 | 
			
		||||
  const contextMenuRef = useRef<ContextMenu | null>(null);
 | 
			
		||||
  const { outCommand } = useMapRootState();
 | 
			
		||||
  const [systems, setSystems] = useState<Node<SolarSystemRawType>[]>();
 | 
			
		||||
 | 
			
		||||
  const { deleteSystems } = useDeleteSystems();
 | 
			
		||||
 | 
			
		||||
  const handleSystemMultipleContext: NodeSelectionMouseHandler = (ev, systems_) => {
 | 
			
		||||
    setSystems(systems_);
 | 
			
		||||
    ev.preventDefault();
 | 
			
		||||
@@ -19,7 +19,7 @@ export const useContextMenuSystemMultipleHandlers = () => {
 | 
			
		||||
    contextMenuRef.current?.show(ev);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const onDeleteSystems = () => {
 | 
			
		||||
  const onDeleteSystems = useCallback(() => {
 | 
			
		||||
    if (!systems) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
@@ -29,12 +29,11 @@ export const useContextMenuSystemMultipleHandlers = () => {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    outCommand({ type: OutCommand.deleteSystems, data: sysToDel });
 | 
			
		||||
  };
 | 
			
		||||
    deleteSystems(sysToDel);
 | 
			
		||||
  }, [deleteSystems, systems]);
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    handleSystemMultipleContext,
 | 
			
		||||
 | 
			
		||||
    contextMenuRef,
 | 
			
		||||
    onDeleteSystems,
 | 
			
		||||
  };
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +1,3 @@
 | 
			
		||||
export * from './useWaypointMenu';
 | 
			
		||||
export * from './useJumpPlannerMenu';
 | 
			
		||||
export * from './useDeleteSystems';
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,18 @@
 | 
			
		||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
 | 
			
		||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
 | 
			
		||||
 | 
			
		||||
export const useDeleteSystems = () => {
 | 
			
		||||
  const { outCommand } = useMapRootState();
 | 
			
		||||
 | 
			
		||||
  const deleteSystems = (systemIds: string[]) => {
 | 
			
		||||
    if (!systemIds || !systemIds.length) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    outCommand({ type: OutCommand.deleteSystems, data: systemIds });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    deleteSystems,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
export * from './useJumpPlannerMenu.tsx';
 | 
			
		||||
@@ -0,0 +1,129 @@
 | 
			
		||||
import { MenuItem } from 'primereact/menuitem';
 | 
			
		||||
import { PrimeIcons } from 'primereact/api';
 | 
			
		||||
import { useCallback } from 'react';
 | 
			
		||||
import { isPossibleSpace } from '@/hooks/Mapper/components/map/helpers/isKnownSpace.ts';
 | 
			
		||||
import { Route } from '@/hooks/Mapper/types/routes.ts';
 | 
			
		||||
import { SolarSystemRawType, SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types';
 | 
			
		||||
import { getSystemById } from '@/hooks/Mapper/helpers';
 | 
			
		||||
import { SOLAR_SYSTEM_CLASS_IDS } from '@/hooks/Mapper/components/map/constants.ts';
 | 
			
		||||
 | 
			
		||||
const imperialSpace = [SOLAR_SYSTEM_CLASS_IDS.hs, SOLAR_SYSTEM_CLASS_IDS.ls, SOLAR_SYSTEM_CLASS_IDS.ns];
 | 
			
		||||
const criminalSpace = [SOLAR_SYSTEM_CLASS_IDS.ls, SOLAR_SYSTEM_CLASS_IDS.ns];
 | 
			
		||||
 | 
			
		||||
enum JUMP_SHIP_TYPE {
 | 
			
		||||
  BLACK_OPS = 'Marshal',
 | 
			
		||||
  JUMP_FREIGHTER = 'Anshar',
 | 
			
		||||
  RORQUAL = 'Rorqual',
 | 
			
		||||
  CAPITAL = 'Thanatos',
 | 
			
		||||
  SUPER_CAPITAL = 'Avatar',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const openJumpPlan = (jumpShipType: JUMP_SHIP_TYPE, from: string, to: string) => {
 | 
			
		||||
  return window.open(`https://evemaps.dotlan.net/jump/${jumpShipType},544/${from}:${to}`, '_blank');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const BRACKET_ICONS = {
 | 
			
		||||
  npcsuperCarrier_32: '/icons/brackets/npcsuperCarrier_32.png',
 | 
			
		||||
  carrier_32: '/icons/brackets/carrier_32.png',
 | 
			
		||||
  battleship_32: '/icons/brackets/battleship_32.png',
 | 
			
		||||
  freighter_32: '/icons/brackets/freighter_32.png',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const renderIcon = (icon: string) => {
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex justify-center items-center mr-1.5 pt-px">
 | 
			
		||||
      <img src={icon} style={{ width: 20, height: 20 }} />
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const useJumpPlannerMenu = (
 | 
			
		||||
  systems: SolarSystemRawType[],
 | 
			
		||||
  systemIdFrom?: string | undefined,
 | 
			
		||||
): ((systemId: SolarSystemStaticInfoRaw, routes: Route[]) => MenuItem[]) => {
 | 
			
		||||
  return useCallback(
 | 
			
		||||
    (destination: SolarSystemStaticInfoRaw) => {
 | 
			
		||||
      if (!destination || !systemIdFrom) {
 | 
			
		||||
        return [];
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const origin = getSystemById(systems, systemIdFrom)?.system_static_info;
 | 
			
		||||
 | 
			
		||||
      if (!origin) {
 | 
			
		||||
        return [];
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const isShowBOorJumpFreighter =
 | 
			
		||||
        isPossibleSpace(imperialSpace, origin.system_class) && isPossibleSpace(criminalSpace, destination.system_class);
 | 
			
		||||
 | 
			
		||||
      const isShowCapital =
 | 
			
		||||
        isPossibleSpace(criminalSpace, origin.system_class) && isPossibleSpace(criminalSpace, destination.system_class);
 | 
			
		||||
 | 
			
		||||
      if (!isShowBOorJumpFreighter && !isShowCapital) {
 | 
			
		||||
        return [];
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return [
 | 
			
		||||
        {
 | 
			
		||||
          label: 'In Jump Planner',
 | 
			
		||||
          icon: PrimeIcons.SEND,
 | 
			
		||||
          items: [
 | 
			
		||||
            ...(isShowBOorJumpFreighter
 | 
			
		||||
              ? [
 | 
			
		||||
                  {
 | 
			
		||||
                    label: 'Black Ops',
 | 
			
		||||
                    icon: renderIcon(BRACKET_ICONS.battleship_32),
 | 
			
		||||
                    command: () => {
 | 
			
		||||
                      openJumpPlan(JUMP_SHIP_TYPE.BLACK_OPS, origin.solar_system_name, destination.solar_system_name);
 | 
			
		||||
                    },
 | 
			
		||||
                  },
 | 
			
		||||
                  {
 | 
			
		||||
                    label: 'Jump Freighter',
 | 
			
		||||
                    icon: renderIcon(BRACKET_ICONS.freighter_32),
 | 
			
		||||
                    command: () => {
 | 
			
		||||
                      openJumpPlan(
 | 
			
		||||
                        JUMP_SHIP_TYPE.JUMP_FREIGHTER,
 | 
			
		||||
                        origin.solar_system_name,
 | 
			
		||||
                        destination.solar_system_name,
 | 
			
		||||
                      );
 | 
			
		||||
                    },
 | 
			
		||||
                  },
 | 
			
		||||
                  {
 | 
			
		||||
                    label: 'Rorqual',
 | 
			
		||||
                    icon: renderIcon(BRACKET_ICONS.freighter_32),
 | 
			
		||||
                    command: () => {
 | 
			
		||||
                      openJumpPlan(JUMP_SHIP_TYPE.RORQUAL, origin.solar_system_name, destination.solar_system_name);
 | 
			
		||||
                    },
 | 
			
		||||
                  },
 | 
			
		||||
                ]
 | 
			
		||||
              : []),
 | 
			
		||||
 | 
			
		||||
            ...(isShowCapital
 | 
			
		||||
              ? [
 | 
			
		||||
                  {
 | 
			
		||||
                    label: 'Capital',
 | 
			
		||||
                    icon: renderIcon(BRACKET_ICONS.carrier_32),
 | 
			
		||||
                    command: () => {
 | 
			
		||||
                      openJumpPlan(JUMP_SHIP_TYPE.CAPITAL, origin.solar_system_name, destination.solar_system_name);
 | 
			
		||||
                    },
 | 
			
		||||
                  },
 | 
			
		||||
                  {
 | 
			
		||||
                    label: 'Super Capital',
 | 
			
		||||
                    icon: renderIcon(BRACKET_ICONS.npcsuperCarrier_32),
 | 
			
		||||
                    command: () => {
 | 
			
		||||
                      openJumpPlan(
 | 
			
		||||
                        JUMP_SHIP_TYPE.SUPER_CAPITAL,
 | 
			
		||||
                        origin.solar_system_name,
 | 
			
		||||
                        destination.solar_system_name,
 | 
			
		||||
                      );
 | 
			
		||||
                    },
 | 
			
		||||
                  },
 | 
			
		||||
                ]
 | 
			
		||||
              : []),
 | 
			
		||||
          ],
 | 
			
		||||
        },
 | 
			
		||||
      ];
 | 
			
		||||
    },
 | 
			
		||||
    [systems, systemIdFrom],
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										2
									
								
								assets/js/hooks/Mapper/components/hooks/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								assets/js/hooks/Mapper/components/hooks/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
export * from './useSystemInfo';
 | 
			
		||||
export * from './useGetOwnOnlineCharacters';
 | 
			
		||||
							
								
								
									
										33
									
								
								assets/js/hooks/Mapper/components/hooks/useSystemInfo.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								assets/js/hooks/Mapper/components/hooks/useSystemInfo.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
 | 
			
		||||
import { useMemo } from 'react';
 | 
			
		||||
import { getSystemById } from '@/hooks/Mapper/helpers';
 | 
			
		||||
import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts';
 | 
			
		||||
 | 
			
		||||
interface UseSystemInfoProps {
 | 
			
		||||
  systemId: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const useSystemInfo = ({ systemId }: UseSystemInfoProps) => {
 | 
			
		||||
  const {
 | 
			
		||||
    data: { systems, connections },
 | 
			
		||||
  } = useMapRootState();
 | 
			
		||||
 | 
			
		||||
  const { systems: systemStatics } = useLoadSystemStatic({ systems: [systemId] });
 | 
			
		||||
 | 
			
		||||
  return useMemo(() => {
 | 
			
		||||
    const staticInfo = systemStatics.get(parseInt(systemId));
 | 
			
		||||
    const dynamicInfo = getSystemById(systems, systemId);
 | 
			
		||||
 | 
			
		||||
    if (!staticInfo || !dynamicInfo) {
 | 
			
		||||
      throw new Error(`Error on getting system ${systemId}`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const leadsTo = connections
 | 
			
		||||
      .filter(x => [x.source, x.target].includes(systemId))
 | 
			
		||||
      .map(x => [x.source, x.target])
 | 
			
		||||
      .flat()
 | 
			
		||||
      .filter(x => x !== systemId);
 | 
			
		||||
 | 
			
		||||
    return { dynamicInfo, staticInfo, leadsTo };
 | 
			
		||||
  }, [systemStatics, systemId, systems, connections]);
 | 
			
		||||
};
 | 
			
		||||
@@ -1,25 +1,25 @@
 | 
			
		||||
import React, { ForwardedRef, forwardRef, MouseEvent, useCallback } from 'react';
 | 
			
		||||
import { ForwardedRef, forwardRef, MouseEvent, useCallback, useEffect } from 'react';
 | 
			
		||||
import ReactFlow, {
 | 
			
		||||
  Background,
 | 
			
		||||
  ConnectionMode,
 | 
			
		||||
  Edge,
 | 
			
		||||
  MiniMap,
 | 
			
		||||
  Node,
 | 
			
		||||
  NodeChange,
 | 
			
		||||
  NodeDragHandler,
 | 
			
		||||
  OnConnect,
 | 
			
		||||
  OnMoveEnd,
 | 
			
		||||
  OnSelectionChangeFunc,
 | 
			
		||||
  SelectionDragHandler,
 | 
			
		||||
  SelectionMode,
 | 
			
		||||
  useEdgesState,
 | 
			
		||||
  useNodesState,
 | 
			
		||||
  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 { useMapHandlers, useUpdateNodes } from './hooks';
 | 
			
		||||
import { useNodesState, useEdgesState, useMapHandlers, useUpdateNodes } from './hooks';
 | 
			
		||||
import { MapHandlers, OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts';
 | 
			
		||||
import {
 | 
			
		||||
  ContextMenuConnection,
 | 
			
		||||
@@ -89,11 +89,14 @@ interface MapCompProps {
 | 
			
		||||
  refn: ForwardedRef<MapHandlers>;
 | 
			
		||||
  onCommand: OutCommandHandler;
 | 
			
		||||
  onSelectionChange: OnMapSelectionChange;
 | 
			
		||||
  onManualDelete(systems: string[]): void;
 | 
			
		||||
  onConnectionInfoClick?(e: SolarSystemConnection): void;
 | 
			
		||||
  onSelectionContextMenu?: NodeSelectionMouseHandler;
 | 
			
		||||
  minimapClasses?: string;
 | 
			
		||||
  isShowMinimap?: boolean;
 | 
			
		||||
  onSystemContextMenu: (event: MouseEvent<Element>, systemId: string) => void;
 | 
			
		||||
  showKSpaceBG?: boolean;
 | 
			
		||||
  isThickConnections?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const MapComp = ({
 | 
			
		||||
@@ -104,16 +107,19 @@ const MapComp = ({
 | 
			
		||||
  onSystemContextMenu,
 | 
			
		||||
  onConnectionInfoClick,
 | 
			
		||||
  onSelectionContextMenu,
 | 
			
		||||
  onManualDelete,
 | 
			
		||||
  isShowMinimap,
 | 
			
		||||
  showKSpaceBG,
 | 
			
		||||
  isThickConnections,
 | 
			
		||||
}: MapCompProps) => {
 | 
			
		||||
  const [nodes, , onNodesChange] = useNodesState<SolarSystemRawType>(initialNodes);
 | 
			
		||||
  const [edges, , onEdgesChange] = useEdgesState<Edge<SolarSystemConnection>[]>(initialEdges);
 | 
			
		||||
  const { getNode } = useReactFlow();
 | 
			
		||||
  const [nodes, , onNodesChange] = useNodesState<Node<SolarSystemRawType>>(initialNodes);
 | 
			
		||||
  const [edges, , onEdgesChange] = useEdgesState<Edge<SolarSystemConnection>>(initialEdges);
 | 
			
		||||
 | 
			
		||||
  useMapHandlers(refn, onSelectionChange);
 | 
			
		||||
  useUpdateNodes(nodes);
 | 
			
		||||
  const { handleRootContext, ...rootCtxProps } = useContextMenuRootHandlers();
 | 
			
		||||
  const { handleConnectionContext, ...connectionCtxProps } = useContextMenuConnectionHandlers();
 | 
			
		||||
 | 
			
		||||
  const { update } = useMapState();
 | 
			
		||||
 | 
			
		||||
  const onConnect: OnConnect = useCallback(
 | 
			
		||||
@@ -169,13 +175,52 @@ const MapComp = ({
 | 
			
		||||
    localStorage.setItem(SESSION_KEY.viewPort, JSON.stringify(viewport));
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleNodesChange = useCallback(
 | 
			
		||||
    (changes: NodeChange[]) => {
 | 
			
		||||
      const systemsIdsToRemove: string[] = [];
 | 
			
		||||
 | 
			
		||||
      const nextChanges = changes.reduce((acc, change) => {
 | 
			
		||||
        if (change.type !== 'remove') {
 | 
			
		||||
          return [...acc, change];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const node = getNode(change.id);
 | 
			
		||||
        if (!node) {
 | 
			
		||||
          return [...acc, change];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (node.data.locked) {
 | 
			
		||||
          return acc;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        systemsIdsToRemove.push(node.data.id);
 | 
			
		||||
        return [...acc, change];
 | 
			
		||||
      }, [] as NodeChange[]);
 | 
			
		||||
 | 
			
		||||
      if (systemsIdsToRemove.length > 0) {
 | 
			
		||||
        onManualDelete(systemsIdsToRemove);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      onNodesChange(nextChanges);
 | 
			
		||||
    },
 | 
			
		||||
    [getNode, onManualDelete, onNodesChange],
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    update(x => ({
 | 
			
		||||
      ...x,
 | 
			
		||||
      showKSpaceBG: showKSpaceBG,
 | 
			
		||||
      isThickConnections: isThickConnections,
 | 
			
		||||
    }));
 | 
			
		||||
  }, [showKSpaceBG, isThickConnections, update]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <div className={classes.MapRoot}>
 | 
			
		||||
        <ReactFlow
 | 
			
		||||
          nodes={nodes}
 | 
			
		||||
          edges={edges}
 | 
			
		||||
          onNodesChange={onNodesChange}
 | 
			
		||||
          onNodesChange={handleNodesChange}
 | 
			
		||||
          onEdgesChange={onEdgesChange}
 | 
			
		||||
          onConnect={onConnect}
 | 
			
		||||
          // TODO we need save into session all of this
 | 
			
		||||
@@ -191,6 +236,7 @@ const MapComp = ({
 | 
			
		||||
          onConnectStart={() => update({ isConnecting: true })}
 | 
			
		||||
          onConnectEnd={() => update({ isConnecting: false })}
 | 
			
		||||
          onNodeMouseEnter={(_, node) => update({ hoverNodeId: node.id })}
 | 
			
		||||
          // onKeyUp=
 | 
			
		||||
          onNodeMouseLeave={() => update({ hoverNodeId: null })}
 | 
			
		||||
          onEdgeClick={(_, t) => {
 | 
			
		||||
            onConnectionInfoClick?.(t.data);
 | 
			
		||||
@@ -210,10 +256,10 @@ const MapComp = ({
 | 
			
		||||
          minZoom={0.2}
 | 
			
		||||
          maxZoom={1.5}
 | 
			
		||||
          elevateNodesOnSelect
 | 
			
		||||
          deleteKeyCode={['Delete']}
 | 
			
		||||
          // TODO need create clear example with problem with that flag
 | 
			
		||||
          //  if system is not visible edge not drawing (and any render in Custom node is not happening)
 | 
			
		||||
          // onlyRenderVisibleElements
 | 
			
		||||
          deleteKeyCode={null}
 | 
			
		||||
          selectionMode={SelectionMode.Partial}
 | 
			
		||||
        >
 | 
			
		||||
          {isShowMinimap && <MiniMap pannable zoomable ariaLabel="Mini map" className={minimapClasses} />}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,8 @@ export type MapData = MapUnionTypes & {
 | 
			
		||||
  isConnecting: boolean;
 | 
			
		||||
  hoverNodeId: string | null;
 | 
			
		||||
  visibleNodes: Set<string>;
 | 
			
		||||
  showKSpaceBG: boolean;
 | 
			
		||||
  isThickConnections: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface MapProviderProps {
 | 
			
		||||
@@ -16,6 +18,7 @@ interface MapProviderProps {
 | 
			
		||||
 | 
			
		||||
const INITIAL_DATA: MapData = {
 | 
			
		||||
  wormholesData: {},
 | 
			
		||||
  wormholes: [],
 | 
			
		||||
  effects: {},
 | 
			
		||||
  characters: [],
 | 
			
		||||
  userCharacters: [],
 | 
			
		||||
@@ -27,6 +30,8 @@ const INITIAL_DATA: MapData = {
 | 
			
		||||
  connections: [],
 | 
			
		||||
  hoverNodeId: null,
 | 
			
		||||
  visibleNodes: new Set(),
 | 
			
		||||
  showKSpaceBG: false,
 | 
			
		||||
  isThickConnections: false,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export interface MapContextProps {
 | 
			
		||||
@@ -38,6 +43,7 @@ export interface MapContextProps {
 | 
			
		||||
const MapContext = createContext<MapContextProps>({
 | 
			
		||||
  update: () => {},
 | 
			
		||||
  data: { ...INITIAL_DATA },
 | 
			
		||||
  // @ts-ignore
 | 
			
		||||
  outCommand: async () => void 0,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.Frigate {
 | 
			
		||||
    stroke: #4e62c9;
 | 
			
		||||
    stroke: #d4f0ff;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.Hovered {
 | 
			
		||||
@@ -37,9 +37,16 @@
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &.Frigate {
 | 
			
		||||
      stroke: #41acd7;
 | 
			
		||||
      stroke: #d4f0ff;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.Tick {
 | 
			
		||||
    stroke-width: 3px;
 | 
			
		||||
 | 
			
		||||
    &.Hovered {
 | 
			
		||||
      stroke-width: 3px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -61,6 +68,14 @@
 | 
			
		||||
      stroke: #ef7dce;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.Tick {
 | 
			
		||||
    stroke-width: 5px;
 | 
			
		||||
 | 
			
		||||
    &.TimeCrit {
 | 
			
		||||
      stroke-width: 6px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.ClickPath {
 | 
			
		||||
@@ -93,5 +108,14 @@
 | 
			
		||||
  width: 5px;
 | 
			
		||||
  height: 5px;
 | 
			
		||||
  z-index: 1001;
 | 
			
		||||
 | 
			
		||||
  &.Tick {
 | 
			
		||||
    width: 7px;
 | 
			
		||||
    height: 7px;
 | 
			
		||||
 | 
			
		||||
    &.Right {
 | 
			
		||||
      margin-left: 0px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,33 +7,54 @@ import clsx from 'clsx';
 | 
			
		||||
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';
 | 
			
		||||
 | 
			
		||||
const MAP_TRANSLATES: Record<string, string> = {
 | 
			
		||||
  [Position.Top]: 'translate(-50%, 0%)',
 | 
			
		||||
  [Position.Top]: 'translate(-48%, 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 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 {
 | 
			
		||||
    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 [edgePath, labelX, labelY] = getBezierPath({
 | 
			
		||||
      sourceX: sx,
 | 
			
		||||
      sourceY: sy,
 | 
			
		||||
      sourceX: sx - offset.x,
 | 
			
		||||
      sourceY: sy - offset.y,
 | 
			
		||||
      sourcePosition: sourcePos,
 | 
			
		||||
      targetPosition: targetPos,
 | 
			
		||||
      targetX: tx,
 | 
			
		||||
      targetY: ty,
 | 
			
		||||
      targetX: tx + offset.x,
 | 
			
		||||
      targetY: ty + offset.y,
 | 
			
		||||
    });
 | 
			
		||||
    return [edgePath, labelX, labelY, sx, sy, tx, ty, sourcePos, targetPos];
 | 
			
		||||
  }, [sourceNode, targetNode]);
 | 
			
		||||
  }, [isThickConnections, sourceNode, targetNode]);
 | 
			
		||||
 | 
			
		||||
  if (!sourceNode || !targetNode || !data) {
 | 
			
		||||
    return null;
 | 
			
		||||
@@ -44,6 +65,7 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
 | 
			
		||||
      <path
 | 
			
		||||
        id={`back_${id}`}
 | 
			
		||||
        className={clsx(classes.EdgePathBack, {
 | 
			
		||||
          [classes.Tick]: isThickConnections,
 | 
			
		||||
          [classes.TimeCrit]: data.time_status === TimeStatus.eol,
 | 
			
		||||
          [classes.Hovered]: hovered,
 | 
			
		||||
        })}
 | 
			
		||||
@@ -54,6 +76,7 @@ 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]: data.mass_status === MassState.verge,
 | 
			
		||||
          [classes.MassHalf]: data.mass_status === MassState.half,
 | 
			
		||||
@@ -75,11 +98,19 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
 | 
			
		||||
 | 
			
		||||
      <EdgeLabelRenderer>
 | 
			
		||||
        <div
 | 
			
		||||
          className={clsx(classes.Handle, 'react-flow__handle absolute nodrag pointer-events-none')}
 | 
			
		||||
          className={clsx(
 | 
			
		||||
            classes.Handle,
 | 
			
		||||
            { [classes.Tick]: isThickConnections, [classes.Right]: Position.Right === sourcePos },
 | 
			
		||||
            'react-flow__handle absolute nodrag pointer-events-none',
 | 
			
		||||
          )}
 | 
			
		||||
          style={{ transform: `${MAP_TRANSLATES[sourcePos]} translate(${sx}px,${sy}px)` }}
 | 
			
		||||
        />
 | 
			
		||||
        <div
 | 
			
		||||
          className={clsx(classes.Handle, 'react-flow__handle absolute nodrag pointer-events-none')}
 | 
			
		||||
          className={clsx(
 | 
			
		||||
            classes.Handle,
 | 
			
		||||
            { [classes.Tick]: isThickConnections },
 | 
			
		||||
            'react-flow__handle absolute nodrag pointer-events-none',
 | 
			
		||||
          )}
 | 
			
		||||
          style={{ transform: `${MAP_TRANSLATES[targetPos]} translate(${tx}px,${ty}px)` }}
 | 
			
		||||
        />
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
@import "@/hooks/Mapper/components/map/styles/eve-common-variables";
 | 
			
		||||
@import '@/hooks/Mapper/components/map/styles/eve-common-variables';
 | 
			
		||||
 | 
			
		||||
$pastel-blue: #5a7d9a;
 | 
			
		||||
$pastel-pink: #d291bc;
 | 
			
		||||
@@ -23,6 +23,62 @@ $tooltip-bg: #202020; // Темный фон для подсказок
 | 
			
		||||
  border-radius: 5px;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  z-index: 1;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
 | 
			
		||||
  &.Mataria,
 | 
			
		||||
  &.Amarria,
 | 
			
		||||
  &.Gallente,
 | 
			
		||||
  &.Caldaria {
 | 
			
		||||
    &::before {
 | 
			
		||||
      content: '';
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      top: 0;
 | 
			
		||||
      left: 0;
 | 
			
		||||
      right: 0;
 | 
			
		||||
      bottom: 0;
 | 
			
		||||
      background-size: cover;
 | 
			
		||||
      background-position: 50% 50%;
 | 
			
		||||
      z-index: -1;
 | 
			
		||||
      background-repeat: no-repeat;
 | 
			
		||||
      border-radius: 3px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.Mataria {
 | 
			
		||||
    &::before {
 | 
			
		||||
      background-image: url('/images/mataria-180.png');
 | 
			
		||||
      opacity: 0.6;
 | 
			
		||||
      background-position-x: 1px;
 | 
			
		||||
      background-position-y: -14px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.Caldaria {
 | 
			
		||||
    &::before {
 | 
			
		||||
      background-image: url('/images/caldaria-180.png');
 | 
			
		||||
      opacity: 0.6;
 | 
			
		||||
      background-position-x: 1px;
 | 
			
		||||
      background-position-y: -10px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.Amarria {
 | 
			
		||||
    &::before {
 | 
			
		||||
      opacity: 0.45;
 | 
			
		||||
      background-image: url('/images/amarr-180.png');
 | 
			
		||||
      background-position-x: 0;
 | 
			
		||||
      background-position-y: -13px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.Gallente {
 | 
			
		||||
    &::before {
 | 
			
		||||
      opacity: 0.5;
 | 
			
		||||
      background-image: url('/images/gallente-180.png');
 | 
			
		||||
      background-position-x: 1px;
 | 
			
		||||
      background-position-y: 0;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.selected {
 | 
			
		||||
    border-color: $pastel-pink;
 | 
			
		||||
@@ -39,7 +95,7 @@ $tooltip-bg: #202020; // Темный фон для подсказок
 | 
			
		||||
 | 
			
		||||
  &.eve-system-status-home {
 | 
			
		||||
    border: 1px solid darken($eve-solar-system-status-color-home, 30%);
 | 
			
		||||
    background-image: linear-gradient(45deg, $eve-solar-system-status-friendly, transparent);
 | 
			
		||||
    background-image: linear-gradient(275deg, $eve-solar-system-status-friendly, transparent);
 | 
			
		||||
 | 
			
		||||
    &.selected {
 | 
			
		||||
      border-color: $eve-solar-system-status-color-home;
 | 
			
		||||
@@ -48,7 +104,7 @@ $tooltip-bg: #202020; // Темный фон для подсказок
 | 
			
		||||
 | 
			
		||||
  &.eve-system-status-friendly {
 | 
			
		||||
    border: 1px solid darken($eve-solar-system-status-color-friendly, 20%);
 | 
			
		||||
    background-image: linear-gradient(45deg, darken($eve-solar-system-status-friendly, 30%), transparent);
 | 
			
		||||
    background-image: linear-gradient(275deg, darken($eve-solar-system-status-friendly, 30%), transparent);
 | 
			
		||||
 | 
			
		||||
    &.selected {
 | 
			
		||||
      border-color: darken($eve-solar-system-status-color-friendly, 5%);
 | 
			
		||||
@@ -57,7 +113,7 @@ $tooltip-bg: #202020; // Темный фон для подсказок
 | 
			
		||||
 | 
			
		||||
  &.eve-system-status-lookingFor {
 | 
			
		||||
    border: 1px solid darken($eve-solar-system-status-color-lookingFor, 15%);
 | 
			
		||||
    background-image: linear-gradient(45deg, #45ff8f2f, #457fff2f);
 | 
			
		||||
    background-image: linear-gradient(275deg, #45ff8f2f, #457fff2f);
 | 
			
		||||
 | 
			
		||||
    &.selected {
 | 
			
		||||
      border-color: $pastel-pink;
 | 
			
		||||
@@ -65,17 +121,16 @@ $tooltip-bg: #202020; // Темный фон для подсказок
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.eve-system-status-warning {
 | 
			
		||||
    background-image: linear-gradient(45deg, $eve-solar-system-status-warning, transparent);
 | 
			
		||||
    background-image: linear-gradient(275deg, $eve-solar-system-status-warning, transparent);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.eve-system-status-dangerous {
 | 
			
		||||
    background-image: linear-gradient(45deg, $eve-solar-system-status-dangerous, transparent);
 | 
			
		||||
    background-image: linear-gradient(275deg, $eve-solar-system-status-dangerous, transparent);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.eve-system-status-target {
 | 
			
		||||
    background-image: linear-gradient(45deg, $eve-solar-system-status-target, transparent);
 | 
			
		||||
    background-image: linear-gradient(275deg, $eve-solar-system-status-target, transparent);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.Bookmarks {
 | 
			
		||||
@@ -102,7 +157,7 @@ $tooltip-bg: #202020; // Темный фон для подсказок
 | 
			
		||||
    //background-color: #833ca4;
 | 
			
		||||
 | 
			
		||||
    &:not(:first-child) {
 | 
			
		||||
      box-shadow: inset 4px -3px 4px rgba(0, 0, 0, .3);
 | 
			
		||||
      box-shadow: inset 4px -3px 4px rgba(0, 0, 0, 0.3);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -125,7 +180,6 @@ $tooltip-bg: #202020; // Темный фон для подсказок
 | 
			
		||||
      font-size: 9px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon {
 | 
			
		||||
@@ -158,14 +212,20 @@ $tooltip-bg: #202020; // Темный фон для подсказок
 | 
			
		||||
    color: #ffb01d;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /* Firefox kostyl */
 | 
			
		||||
  @-moz-document url-prefix() {
 | 
			
		||||
    .classSystemName {
 | 
			
		||||
      font-family: inherit !important;
 | 
			
		||||
      font-weight: bold;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .classSystemName {
 | 
			
		||||
    //font-weight: bold;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .solarSystemName {
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.BottomRow {
 | 
			
		||||
@@ -210,6 +270,13 @@ $tooltip-bg: #202020; // Темный фон для подсказок
 | 
			
		||||
  & > * {
 | 
			
		||||
    line-height: 10px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /* Firefox kostyl */
 | 
			
		||||
  @-moz-document url-prefix() {
 | 
			
		||||
    position: relative;
 | 
			
		||||
    top: -1px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.Handlers {
 | 
			
		||||
@@ -232,11 +299,40 @@ $tooltip-bg: #202020; // Темный фон для подсказок
 | 
			
		||||
    border-color: $pastel-pink;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.HandleTop { top: -2px }
 | 
			
		||||
  &.HandleTop {
 | 
			
		||||
    top: -2px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.HandleRight { right: -2px }
 | 
			
		||||
  &.HandleRight {
 | 
			
		||||
    right: -2px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.HandleBottom { bottom: -2px }
 | 
			
		||||
  &.HandleBottom {
 | 
			
		||||
    bottom: -2px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.HandleLeft { left: -2px }
 | 
			
		||||
  &.HandleLeft {
 | 
			
		||||
    left: -2px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.Tick {
 | 
			
		||||
    width: 7px;
 | 
			
		||||
    height: 7px;
 | 
			
		||||
 | 
			
		||||
    &.HandleTop {
 | 
			
		||||
      top: -3px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &.HandleRight {
 | 
			
		||||
      right: -3px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &.HandleBottom {
 | 
			
		||||
      bottom: -3px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &.HandleLeft {
 | 
			
		||||
      left: -3px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -19,9 +19,17 @@ 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 === null) {
 | 
			
		||||
  if (!labels) {
 | 
			
		||||
    return [];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -50,6 +58,7 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
 | 
			
		||||
    statics,
 | 
			
		||||
    effect_name,
 | 
			
		||||
    region_name,
 | 
			
		||||
    region_id,
 | 
			
		||||
    is_shattered,
 | 
			
		||||
    solar_system_name,
 | 
			
		||||
  } = data.system_static_info;
 | 
			
		||||
@@ -69,6 +78,8 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
 | 
			
		||||
      isConnecting,
 | 
			
		||||
      hoverNodeId,
 | 
			
		||||
      visibleNodes,
 | 
			
		||||
      showKSpaceBG,
 | 
			
		||||
      isThickConnections,
 | 
			
		||||
    },
 | 
			
		||||
    outCommand,
 | 
			
		||||
  } = useMapState();
 | 
			
		||||
@@ -114,13 +125,16 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
 | 
			
		||||
 | 
			
		||||
  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)}>
 | 
			
		||||
              <div>{labelCustom}</div>
 | 
			
		||||
              <span className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] ">{labelCustom}</span>
 | 
			
		||||
            </div>
 | 
			
		||||
          )}
 | 
			
		||||
 | 
			
		||||
@@ -147,18 +161,24 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
 | 
			
		||||
        </div>
 | 
			
		||||
      )}
 | 
			
		||||
 | 
			
		||||
      <div className={clsx(classes.RootCustomNode, classes[STATUS_CLASSES[status]], { [classes.selected]: selected })}>
 | 
			
		||||
      <div
 | 
			
		||||
        className={clsx(classes.RootCustomNode, regionClass, classes[STATUS_CLASSES[status]], {
 | 
			
		||||
          [classes.selected]: selected,
 | 
			
		||||
        })}
 | 
			
		||||
      >
 | 
			
		||||
        {visible && (
 | 
			
		||||
          <>
 | 
			
		||||
            <div className={classes.HeadRow}>
 | 
			
		||||
              <div className={clsx(classes.classTitle, classTitleColor)}>{class_title ?? '-'}</div>
 | 
			
		||||
              <div className={clsx(classes.classTitle, classTitleColor, '[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]')}>
 | 
			
		||||
                {class_title ?? '-'}
 | 
			
		||||
              </div>
 | 
			
		||||
              {tag != null && tag !== '' && (
 | 
			
		||||
                <div className={clsx(classes.TagTitle, 'text-sky-400 font-medium')}>{tag}</div>
 | 
			
		||||
              )}
 | 
			
		||||
              <div
 | 
			
		||||
                className={clsx(
 | 
			
		||||
                  classes.classSystemName,
 | 
			
		||||
                  'flex-grow overflow-hidden text-ellipsis whitespace-nowrap font-sans',
 | 
			
		||||
                  '[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] flex-grow overflow-hidden text-ellipsis whitespace-nowrap font-sans',
 | 
			
		||||
                )}
 | 
			
		||||
              >
 | 
			
		||||
                {solar_system_name}
 | 
			
		||||
@@ -179,11 +199,17 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
 | 
			
		||||
 | 
			
		||||
            <div className={clsx(classes.BottomRow, 'flex items-center justify-between')}>
 | 
			
		||||
              {customName && (
 | 
			
		||||
                <div className="text-blue-300 whitespace-nowrap overflow-hidden text-ellipsis mr-0.5">{customName}</div>
 | 
			
		||||
                <div className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] text-blue-300 whitespace-nowrap overflow-hidden text-ellipsis mr-0.5">
 | 
			
		||||
                  {customName}
 | 
			
		||||
                </div>
 | 
			
		||||
              )}
 | 
			
		||||
 | 
			
		||||
              {!isWormhole && !customName && (
 | 
			
		||||
                <div className="text-stone-400 whitespace-nowrap overflow-hidden text-ellipsis mr-0.5">
 | 
			
		||||
                <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>
 | 
			
		||||
              )}
 | 
			
		||||
@@ -192,10 +218,10 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
 | 
			
		||||
 | 
			
		||||
              <div className="flex items-center justify-end">
 | 
			
		||||
                <div className="flex gap-1 items-center">
 | 
			
		||||
                  {locked && <i className={PrimeIcons.LOCK} style={{ fontSize: '0.45rem' }}></i>}
 | 
			
		||||
                  {locked && <i className={PrimeIcons.LOCK} style={{ fontSize: '0.45rem', fontWeight: 'bold' }}></i>}
 | 
			
		||||
 | 
			
		||||
                  {hubs.includes(solar_system_id.toString()) && (
 | 
			
		||||
                    <i className={PrimeIcons.MAP_MARKER} style={{ fontSize: '0.45rem' }}></i>
 | 
			
		||||
                    <i className={PrimeIcons.MAP_MARKER} style={{ fontSize: '0.45rem', fontWeight: 'bold' }}></i>
 | 
			
		||||
                  )}
 | 
			
		||||
 | 
			
		||||
                  {charactersInSystem.length > 0 && (
 | 
			
		||||
@@ -214,28 +240,40 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
 | 
			
		||||
      <div onMouseDownCapture={dbClick} className={classes.Handlers}>
 | 
			
		||||
        <Handle
 | 
			
		||||
          type="source"
 | 
			
		||||
          className={clsx(classes.Handle, classes.HandleTop, { [classes.selected]: selected })}
 | 
			
		||||
          className={clsx(classes.Handle, classes.HandleTop, {
 | 
			
		||||
            [classes.selected]: selected,
 | 
			
		||||
            [classes.Tick]: isThickConnections,
 | 
			
		||||
          })}
 | 
			
		||||
          style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
 | 
			
		||||
          position={Position.Top}
 | 
			
		||||
          id="a"
 | 
			
		||||
        />
 | 
			
		||||
        <Handle
 | 
			
		||||
          type="source"
 | 
			
		||||
          className={clsx(classes.Handle, classes.HandleRight, { [classes.selected]: selected })}
 | 
			
		||||
          className={clsx(classes.Handle, classes.HandleRight, {
 | 
			
		||||
            [classes.selected]: selected,
 | 
			
		||||
            [classes.Tick]: isThickConnections,
 | 
			
		||||
          })}
 | 
			
		||||
          style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
 | 
			
		||||
          position={Position.Right}
 | 
			
		||||
          id="b"
 | 
			
		||||
        />
 | 
			
		||||
        <Handle
 | 
			
		||||
          type="source"
 | 
			
		||||
          className={clsx(classes.Handle, classes.HandleBottom, { [classes.selected]: selected })}
 | 
			
		||||
          className={clsx(classes.Handle, classes.HandleBottom, {
 | 
			
		||||
            [classes.selected]: selected,
 | 
			
		||||
            [classes.Tick]: isThickConnections,
 | 
			
		||||
          })}
 | 
			
		||||
          style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
 | 
			
		||||
          position={Position.Bottom}
 | 
			
		||||
          id="c"
 | 
			
		||||
        />
 | 
			
		||||
        <Handle
 | 
			
		||||
          type="source"
 | 
			
		||||
          className={clsx(classes.Handle, classes.HandleLeft, { [classes.selected]: selected })}
 | 
			
		||||
          className={clsx(classes.Handle, classes.HandleLeft, {
 | 
			
		||||
            [classes.selected]: selected,
 | 
			
		||||
            [classes.Tick]: isThickConnections,
 | 
			
		||||
          })}
 | 
			
		||||
          style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
 | 
			
		||||
          position={Position.Left}
 | 
			
		||||
          id="d"
 | 
			
		||||
 
 | 
			
		||||
@@ -18,5 +18,9 @@ export const WormholeClassComp = ({ id }: WormholeClassComp) => {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const colorClass = WORMHOLE_CLASS_STYLES[wormholeDataAdditional.wormholeClassID.toString()];
 | 
			
		||||
  return <div className={clsx(colorClass)}>{wormholeDataAdditional.shortName}</div>;
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={clsx(colorClass, '[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]')}>
 | 
			
		||||
      {wormholeDataAdditional.shortName}
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -30,11 +30,77 @@ export enum SOLAR_SYSTEM_CLASS_IDS {
 | 
			
		||||
  zarzakh = 10100,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export enum SOLAR_SYSTEM_CLASS_GROUPS {
 | 
			
		||||
  ccp = 'ccp',
 | 
			
		||||
  c1 = 'c1',
 | 
			
		||||
  c2 = 'c2',
 | 
			
		||||
  c3 = 'c3',
 | 
			
		||||
  c4 = 'c4',
 | 
			
		||||
  c5 = 'c5',
 | 
			
		||||
  c6 = 'c6',
 | 
			
		||||
  hs = 'hs',
 | 
			
		||||
  ls = 'ls',
 | 
			
		||||
  ns = 'ns',
 | 
			
		||||
  thera = 'thera',
 | 
			
		||||
  c13 = 'c13',
 | 
			
		||||
  drifter = 'drifter',
 | 
			
		||||
  unknown = 'unknown',
 | 
			
		||||
  pochven = 'pochven',
 | 
			
		||||
  jovian = 'jovian',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const SOLAR_SYSTEM_TO_CLASS_GROUPS_CLASSES = {
 | 
			
		||||
  c1: ['c1'],
 | 
			
		||||
  c2: ['c2'],
 | 
			
		||||
  c3: ['c3'],
 | 
			
		||||
  c4: ['c4'],
 | 
			
		||||
  c5: ['c5'],
 | 
			
		||||
  c6: ['c6'],
 | 
			
		||||
  hs: ['hs'],
 | 
			
		||||
  ls: ['ls'],
 | 
			
		||||
  ns: ['ns'],
 | 
			
		||||
  thera: ['thera'],
 | 
			
		||||
  c13: ['c13'],
 | 
			
		||||
  pochven: ['pochven'],
 | 
			
		||||
  drifter: ['sentinel', 'barbican', 'vidette', 'conflux', 'redoubt'],
 | 
			
		||||
  jove: ['jove'],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS = {
 | 
			
		||||
  ccp1: SOLAR_SYSTEM_CLASS_GROUPS.ccp,
 | 
			
		||||
  c1: SOLAR_SYSTEM_CLASS_GROUPS.c1,
 | 
			
		||||
  c2: SOLAR_SYSTEM_CLASS_GROUPS.c2,
 | 
			
		||||
  c3: SOLAR_SYSTEM_CLASS_GROUPS.c3,
 | 
			
		||||
  c4: SOLAR_SYSTEM_CLASS_GROUPS.c4,
 | 
			
		||||
  c5: SOLAR_SYSTEM_CLASS_GROUPS.c5,
 | 
			
		||||
  c6: SOLAR_SYSTEM_CLASS_GROUPS.c6,
 | 
			
		||||
  hs: SOLAR_SYSTEM_CLASS_GROUPS.hs,
 | 
			
		||||
  ls: SOLAR_SYSTEM_CLASS_GROUPS.ls,
 | 
			
		||||
  ns: SOLAR_SYSTEM_CLASS_GROUPS.ns,
 | 
			
		||||
  ccp2: SOLAR_SYSTEM_CLASS_GROUPS.ccp,
 | 
			
		||||
  ccp3: SOLAR_SYSTEM_CLASS_GROUPS.ccp,
 | 
			
		||||
  thera: SOLAR_SYSTEM_CLASS_GROUPS.thera,
 | 
			
		||||
  c13: SOLAR_SYSTEM_CLASS_GROUPS.c13,
 | 
			
		||||
  sentinel: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
 | 
			
		||||
  baribican: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
 | 
			
		||||
  vidette: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
 | 
			
		||||
  conflux: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
 | 
			
		||||
  redoubt: SOLAR_SYSTEM_CLASS_GROUPS.drifter,
 | 
			
		||||
  a1: SOLAR_SYSTEM_CLASS_GROUPS.unknown,
 | 
			
		||||
  a2: SOLAR_SYSTEM_CLASS_GROUPS.unknown,
 | 
			
		||||
  a3: SOLAR_SYSTEM_CLASS_GROUPS.unknown,
 | 
			
		||||
  a4: SOLAR_SYSTEM_CLASS_GROUPS.unknown,
 | 
			
		||||
  a5: SOLAR_SYSTEM_CLASS_GROUPS.unknown,
 | 
			
		||||
  ccp4: SOLAR_SYSTEM_CLASS_GROUPS.ccp,
 | 
			
		||||
  pochven: SOLAR_SYSTEM_CLASS_GROUPS.pochven,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type WormholesAdditionalInfoType = {
 | 
			
		||||
  id: string;
 | 
			
		||||
  shortName: string;
 | 
			
		||||
  wormholeClassID: number;
 | 
			
		||||
  title: string;
 | 
			
		||||
  shortTitle: string;
 | 
			
		||||
  effectPower?: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -45,6 +111,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
 | 
			
		||||
    shortName: 'CCP',
 | 
			
		||||
    wormholeClassID: -1,
 | 
			
		||||
    title: 'CCP System',
 | 
			
		||||
    shortTitle: 'CCP',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: 'c1',
 | 
			
		||||
@@ -52,6 +119,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
 | 
			
		||||
    wormholeClassID: 1,
 | 
			
		||||
    effectPower: 1,
 | 
			
		||||
    title: 'Class 1',
 | 
			
		||||
    shortTitle: 'C1',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: 'c2',
 | 
			
		||||
@@ -59,6 +127,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
 | 
			
		||||
    wormholeClassID: 2,
 | 
			
		||||
    effectPower: 2,
 | 
			
		||||
    title: 'Class 2',
 | 
			
		||||
    shortTitle: 'C2',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: 'c3',
 | 
			
		||||
@@ -66,6 +135,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
 | 
			
		||||
    wormholeClassID: 3,
 | 
			
		||||
    effectPower: 3,
 | 
			
		||||
    title: 'Class 3',
 | 
			
		||||
    shortTitle: 'C3',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: 'c4',
 | 
			
		||||
@@ -73,6 +143,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
 | 
			
		||||
    wormholeClassID: 4,
 | 
			
		||||
    effectPower: 4,
 | 
			
		||||
    title: 'Class 4',
 | 
			
		||||
    shortTitle: 'C4',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: 'c5',
 | 
			
		||||
@@ -80,6 +151,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
 | 
			
		||||
    wormholeClassID: 5,
 | 
			
		||||
    effectPower: 5,
 | 
			
		||||
    title: 'Class 5',
 | 
			
		||||
    shortTitle: 'C5',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: 'c6',
 | 
			
		||||
@@ -87,42 +159,49 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
 | 
			
		||||
    wormholeClassID: 6,
 | 
			
		||||
    effectPower: 6,
 | 
			
		||||
    title: 'Class 6',
 | 
			
		||||
    shortTitle: 'C6',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: 'hs',
 | 
			
		||||
    shortName: 'H',
 | 
			
		||||
    wormholeClassID: 7,
 | 
			
		||||
    title: 'High-sec',
 | 
			
		||||
    shortTitle: 'High-sec',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: 'ls',
 | 
			
		||||
    shortName: 'L',
 | 
			
		||||
    wormholeClassID: 8,
 | 
			
		||||
    title: 'Low-sec',
 | 
			
		||||
    shortTitle: 'Low-sec',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: 'ns',
 | 
			
		||||
    shortName: 'N',
 | 
			
		||||
    wormholeClassID: 9,
 | 
			
		||||
    title: 'Null-sec',
 | 
			
		||||
    shortTitle: 'Null-sec',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: 'ccp2',
 | 
			
		||||
    shortName: 'CCP',
 | 
			
		||||
    wormholeClassID: 10,
 | 
			
		||||
    title: 'CCP System',
 | 
			
		||||
    shortTitle: 'CCP',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: 'ccp3',
 | 
			
		||||
    shortName: 'CCP',
 | 
			
		||||
    wormholeClassID: 11,
 | 
			
		||||
    title: 'CCP System',
 | 
			
		||||
    shortTitle: 'CCP',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: 'thera',
 | 
			
		||||
    shortName: 'T',
 | 
			
		||||
    wormholeClassID: 12,
 | 
			
		||||
    title: 'Class 12 (Thera)',
 | 
			
		||||
    shortTitle: 'Thera',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: 'c13',
 | 
			
		||||
@@ -130,6 +209,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
 | 
			
		||||
    wormholeClassID: 13,
 | 
			
		||||
    effectPower: 6,
 | 
			
		||||
    title: 'Class 13 (Shattered Frigate)',
 | 
			
		||||
    shortTitle: 'C13',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: 'sentinel',
 | 
			
		||||
@@ -137,6 +217,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
 | 
			
		||||
    wormholeClassID: 14,
 | 
			
		||||
    effectPower: 2,
 | 
			
		||||
    title: 'Class 14 (Sentinel Drifter)',
 | 
			
		||||
    shortTitle: 'Sentinel',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: 'barbican',
 | 
			
		||||
@@ -144,6 +225,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
 | 
			
		||||
    wormholeClassID: 15,
 | 
			
		||||
    effectPower: 2,
 | 
			
		||||
    title: 'Class 15 (Barbican Drifter)',
 | 
			
		||||
    shortTitle: 'Barbican',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: 'vidette',
 | 
			
		||||
@@ -151,6 +233,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
 | 
			
		||||
    wormholeClassID: 16,
 | 
			
		||||
    effectPower: 2,
 | 
			
		||||
    title: 'Class 16 (Vidette Drifter)',
 | 
			
		||||
    shortTitle: 'Vidette',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: 'conflux',
 | 
			
		||||
@@ -158,6 +241,7 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
 | 
			
		||||
    wormholeClassID: 17,
 | 
			
		||||
    effectPower: 2,
 | 
			
		||||
    title: 'Class 17 (Conflux Drifter)',
 | 
			
		||||
    shortTitle: 'Conflux',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: 'redoubt',
 | 
			
		||||
@@ -165,59 +249,79 @@ export const WORMHOLES_ADDITIONAL_INFO_RAW: WormholesAdditionalInfoType[] = [
 | 
			
		||||
    wormholeClassID: 18,
 | 
			
		||||
    effectPower: 2,
 | 
			
		||||
    title: 'Class 18 (Redoubt Drifter)',
 | 
			
		||||
    shortTitle: 'Redoubt',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: 'a1',
 | 
			
		||||
    shortName: 'A1',
 | 
			
		||||
    wormholeClassID: 19,
 | 
			
		||||
    title: '(Abyssal class 1)',
 | 
			
		||||
    shortTitle: 'A1',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: 'a2',
 | 
			
		||||
    shortName: 'A2',
 | 
			
		||||
    wormholeClassID: 20,
 | 
			
		||||
    title: '(Abyssal class 2)',
 | 
			
		||||
    shortTitle: 'A2',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: 'a3',
 | 
			
		||||
    shortName: 'A3',
 | 
			
		||||
    wormholeClassID: 21,
 | 
			
		||||
    title: '(Abyssal class 3)',
 | 
			
		||||
    shortTitle: 'A3',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: 'a4',
 | 
			
		||||
    shortName: 'A4',
 | 
			
		||||
    wormholeClassID: 22,
 | 
			
		||||
    title: '(Abyssal class 4)',
 | 
			
		||||
    shortTitle: 'A4',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: 'a5',
 | 
			
		||||
    shortName: 'A5',
 | 
			
		||||
    wormholeClassID: 23,
 | 
			
		||||
    title: '(Abyssal class 5)',
 | 
			
		||||
    shortTitle: 'A5',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: 'ccp4',
 | 
			
		||||
    shortName: 'CCP',
 | 
			
		||||
    wormholeClassID: 24,
 | 
			
		||||
    title: 'CCP System (Penalty)',
 | 
			
		||||
    shortTitle: 'CCP',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: 'pochven',
 | 
			
		||||
    shortName: 'P',
 | 
			
		||||
    wormholeClassID: 25,
 | 
			
		||||
    title: 'Triglavian space (Pochven)',
 | 
			
		||||
    shortTitle: 'Pochven',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: 'zarzakh',
 | 
			
		||||
    shortName: 'N',
 | 
			
		||||
    wormholeClassID: 10100,
 | 
			
		||||
    title: 'Pirate space',
 | 
			
		||||
    shortTitle: 'Zarzakh',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: 'k162',
 | 
			
		||||
    shortName: 'K162',
 | 
			
		||||
    wormholeClassID: 10101,
 | 
			
		||||
    title: 'Reverse',
 | 
			
		||||
    shortTitle: 'K162',
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export const WORMHOLES_ADDITIONAL_INFO: Record<string, WormholesAdditionalInfoType> =
 | 
			
		||||
  WORMHOLES_ADDITIONAL_INFO_RAW.reduce((acc, x) => ({ ...acc, [x.id]: x }), {});
 | 
			
		||||
 | 
			
		||||
export const WORMHOLES_ADDITIONAL_INFO_BY_CLASS_ID: Record<string, WormholesAdditionalInfoType> =
 | 
			
		||||
  WORMHOLES_ADDITIONAL_INFO_RAW.reduce((acc, x) => ({ ...acc, [x.wormholeClassID]: x }), {});
 | 
			
		||||
 | 
			
		||||
// export const SOLAR_SYSTEM_CLASS_NAMES = {
 | 
			
		||||
//   ccp1 =  ,
 | 
			
		||||
//   c1 = ,
 | 
			
		||||
 
 | 
			
		||||
@@ -11,3 +11,7 @@ export const isKnownSpace = (wormholeClassID: number) => {
 | 
			
		||||
 | 
			
		||||
  return false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const isPossibleSpace = (spaces: number[], wormholeClassID: number) => {
 | 
			
		||||
  return spaces.includes(wormholeClassID);
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -5,5 +5,6 @@ export * from './useMapRemoveSystems';
 | 
			
		||||
export * from './useCommandsCharacters';
 | 
			
		||||
export * from './useCommandsConnections';
 | 
			
		||||
export * from './useCommandsConnections';
 | 
			
		||||
export * from './useCenterSystem';
 | 
			
		||||
export * from './useSelectSystem';
 | 
			
		||||
export * from './useMapCommands';
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,18 @@
 | 
			
		||||
import { useReactFlow } from 'reactflow';
 | 
			
		||||
import { useCallback, useRef } from 'react';
 | 
			
		||||
import { CommandCenterSystem } from '@/hooks/Mapper/types';
 | 
			
		||||
 | 
			
		||||
export const useCenterSystem = () => {
 | 
			
		||||
  const rf = useReactFlow();
 | 
			
		||||
 | 
			
		||||
  const ref = useRef({ rf });
 | 
			
		||||
  ref.current = { rf };
 | 
			
		||||
 | 
			
		||||
  return useCallback((systemId: CommandCenterSystem) => {
 | 
			
		||||
    const systemNode = ref.current.rf.getNodes().find(x => x.data.id === systemId);
 | 
			
		||||
    if (!systemNode) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    ref.current.rf.setCenter(systemNode.position.x, systemNode.position.y, { duration: 1000 });
 | 
			
		||||
  }, []);
 | 
			
		||||
};
 | 
			
		||||
@@ -2,28 +2,18 @@ import { Node, useReactFlow } from 'reactflow';
 | 
			
		||||
import { useCallback, useRef } from 'react';
 | 
			
		||||
import { CommandAddSystems } from '@/hooks/Mapper/types/mapHandlers.ts';
 | 
			
		||||
import { convertSystem2Node } from '../../helpers';
 | 
			
		||||
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
 | 
			
		||||
 | 
			
		||||
export const useMapAddSystems = () => {
 | 
			
		||||
  const rf = useReactFlow();
 | 
			
		||||
  const {
 | 
			
		||||
    data: { systems },
 | 
			
		||||
    update,
 | 
			
		||||
  } = useMapState();
 | 
			
		||||
 | 
			
		||||
  const ref = useRef({ rf, systems, update });
 | 
			
		||||
  ref.current = { update, systems, rf };
 | 
			
		||||
  const ref = useRef({ rf });
 | 
			
		||||
  ref.current = { rf };
 | 
			
		||||
 | 
			
		||||
  return useCallback(
 | 
			
		||||
    (systems: CommandAddSystems) => {
 | 
			
		||||
      const nodes = rf.getNodes();
 | 
			
		||||
      const prepared: Node[] = systems.filter(x => !nodes.some(y => x.id === y.id)).map(convertSystem2Node);
 | 
			
		||||
      rf.addNodes(prepared);
 | 
			
		||||
  return useCallback((systems: CommandAddSystems) => {
 | 
			
		||||
    const { rf } = ref.current;
 | 
			
		||||
    const nodes = rf.getNodes();
 | 
			
		||||
 | 
			
		||||
      ref.current.update({
 | 
			
		||||
        systems: [...ref.current.systems.filter(sys => systems.some(x => sys.id !== x.id)), ...systems],
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    [rf],
 | 
			
		||||
  );
 | 
			
		||||
    const prepared: Node[] = systems.filter(x => !nodes.some(y => x.id === y.id)).map(convertSystem2Node);
 | 
			
		||||
    rf.addNodes(prepared);
 | 
			
		||||
  }, []);
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -5,24 +5,21 @@ import { OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts
 | 
			
		||||
 | 
			
		||||
export const useMapRemoveSystems = (onSelectionChange: OnMapSelectionChange) => {
 | 
			
		||||
  const rf = useReactFlow();
 | 
			
		||||
  const ref = useRef({ onSelectionChange });
 | 
			
		||||
  ref.current = { onSelectionChange };
 | 
			
		||||
  const ref = useRef({ onSelectionChange, rf });
 | 
			
		||||
  ref.current = { onSelectionChange, rf };
 | 
			
		||||
 | 
			
		||||
  return useCallback(
 | 
			
		||||
    (systems: CommandRemoveSystems) => {
 | 
			
		||||
      rf.deleteElements({ nodes: systems.map(x => ({ id: `${x}` })) });
 | 
			
		||||
  return useCallback((systems: CommandRemoveSystems) => {
 | 
			
		||||
    ref.current.rf.deleteElements({ nodes: systems.map(x => ({ id: `${x}` })) });
 | 
			
		||||
 | 
			
		||||
      const newSelection = rf
 | 
			
		||||
        .getNodes()
 | 
			
		||||
        .filter(x => !systems.includes(parseInt(x.id)))
 | 
			
		||||
        .filter(x => x.selected)
 | 
			
		||||
        .map(x => x.id);
 | 
			
		||||
    const newSelection = ref.current.rf
 | 
			
		||||
      .getNodes()
 | 
			
		||||
      .filter(x => !systems.includes(parseInt(x.id)))
 | 
			
		||||
      .filter(x => x.selected)
 | 
			
		||||
      .map(x => x.id);
 | 
			
		||||
 | 
			
		||||
      ref.current.onSelectionChange({
 | 
			
		||||
        systems: newSelection,
 | 
			
		||||
        connections: [],
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    [rf],
 | 
			
		||||
  );
 | 
			
		||||
    ref.current.onSelectionChange({
 | 
			
		||||
      systems: newSelection,
 | 
			
		||||
      connections: [],
 | 
			
		||||
    });
 | 
			
		||||
  }, []);
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -4,18 +4,18 @@ import { CommandSelectSystem } from '@/hooks/Mapper/types';
 | 
			
		||||
 | 
			
		||||
export const useSelectSystem = () => {
 | 
			
		||||
  const rf = useReactFlow();
 | 
			
		||||
 | 
			
		||||
  const ref = useRef({ rf });
 | 
			
		||||
  ref.current = { rf };
 | 
			
		||||
 | 
			
		||||
  return useCallback((systemId: CommandSelectSystem) => {
 | 
			
		||||
    if (!ref.current?.rf) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    const systemNode = ref.current.rf.getNodes().find(x => x.data.id === systemId);
 | 
			
		||||
    if (!systemNode) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ref.current.rf.setCenter(systemNode.position.x, systemNode.position.y, { duration: 1000 });
 | 
			
		||||
    ref.current.rf.setNodes(nds =>
 | 
			
		||||
      nds.map(node => {
 | 
			
		||||
        return {
 | 
			
		||||
          ...node,
 | 
			
		||||
          selected: node.id === systemId,
 | 
			
		||||
        };
 | 
			
		||||
      }),
 | 
			
		||||
    );
 | 
			
		||||
  }, []);
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,2 +1,3 @@
 | 
			
		||||
export * from './useMapHandlers';
 | 
			
		||||
export * from './useUpdateNodes';
 | 
			
		||||
export * from './useNodesEdgesState';
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import { ForwardedRef, useImperativeHandle } from 'react';
 | 
			
		||||
import { ForwardedRef, useImperativeHandle, useRef } from 'react';
 | 
			
		||||
import {
 | 
			
		||||
  CommandAddConnections,
 | 
			
		||||
  CommandAddSystems,
 | 
			
		||||
@@ -18,6 +18,7 @@ import {
 | 
			
		||||
  CommandUpdateSystems,
 | 
			
		||||
  MapHandlers,
 | 
			
		||||
} from '@/hooks/Mapper/types/mapHandlers.ts';
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  useCommandsCharacters,
 | 
			
		||||
  useCommandsConnections,
 | 
			
		||||
@@ -26,6 +27,7 @@ import {
 | 
			
		||||
  useMapInit,
 | 
			
		||||
  useMapRemoveSystems,
 | 
			
		||||
  useMapUpdateSystems,
 | 
			
		||||
  useCenterSystem,
 | 
			
		||||
  useSelectSystem,
 | 
			
		||||
} from './api';
 | 
			
		||||
import { OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts';
 | 
			
		||||
@@ -35,8 +37,12 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
 | 
			
		||||
  const mapAddSystems = useMapAddSystems();
 | 
			
		||||
  const mapUpdateSystems = useMapUpdateSystems();
 | 
			
		||||
  const removeSystems = useMapRemoveSystems(onSelectionChange);
 | 
			
		||||
  const centerSystem = useCenterSystem();
 | 
			
		||||
  const selectSystem = useSelectSystem();
 | 
			
		||||
 | 
			
		||||
  const selectRef = useRef({ onSelectionChange });
 | 
			
		||||
  selectRef.current = { onSelectionChange };
 | 
			
		||||
 | 
			
		||||
  const { addConnections, removeConnections, updateConnection } = useCommandsConnections();
 | 
			
		||||
  const { mapUpdated, killsUpdated } = useMapCommands();
 | 
			
		||||
  const { charactersUpdated, presentCharacters, characterAdded, characterRemoved, characterUpdated } =
 | 
			
		||||
@@ -52,16 +58,16 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
 | 
			
		||||
              mapInit(data as CommandInit);
 | 
			
		||||
              break;
 | 
			
		||||
            case Commands.addSystems:
 | 
			
		||||
              mapAddSystems(data as CommandAddSystems);
 | 
			
		||||
              setTimeout(() => mapAddSystems(data as CommandAddSystems), 100);
 | 
			
		||||
              break;
 | 
			
		||||
            case Commands.updateSystems:
 | 
			
		||||
              mapUpdateSystems(data as CommandUpdateSystems);
 | 
			
		||||
              break;
 | 
			
		||||
            case Commands.removeSystems:
 | 
			
		||||
              removeSystems(data as CommandRemoveSystems);
 | 
			
		||||
              setTimeout(() => removeSystems(data as CommandRemoveSystems), 100);
 | 
			
		||||
              break;
 | 
			
		||||
            case Commands.addConnections:
 | 
			
		||||
              addConnections(data as CommandAddConnections);
 | 
			
		||||
              setTimeout(() => addConnections(data as CommandAddConnections), 100);
 | 
			
		||||
              break;
 | 
			
		||||
            case Commands.removeConnections:
 | 
			
		||||
              removeConnections(data as CommandRemoveConnections);
 | 
			
		||||
@@ -91,14 +97,36 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
 | 
			
		||||
              killsUpdated(data as CommandKillsUpdated);
 | 
			
		||||
              break;
 | 
			
		||||
 | 
			
		||||
            case Commands.centerSystem:
 | 
			
		||||
              setTimeout(() => {
 | 
			
		||||
                const systemId = `${data}`;
 | 
			
		||||
                centerSystem(systemId as CommandSelectSystem);
 | 
			
		||||
              }, 100);
 | 
			
		||||
              break;
 | 
			
		||||
 | 
			
		||||
            case Commands.selectSystem:
 | 
			
		||||
              selectSystem(data as CommandSelectSystem);
 | 
			
		||||
              setTimeout(() => {
 | 
			
		||||
                const systemId = `${data}`;
 | 
			
		||||
                selectRef.current.onSelectionChange({
 | 
			
		||||
                  systems: [systemId],
 | 
			
		||||
                  connections: [],
 | 
			
		||||
                });
 | 
			
		||||
                selectSystem(systemId as CommandSelectSystem);
 | 
			
		||||
              }, 500);
 | 
			
		||||
              break;
 | 
			
		||||
 | 
			
		||||
            case Commands.routes:
 | 
			
		||||
              // do nothing here
 | 
			
		||||
              break;
 | 
			
		||||
 | 
			
		||||
            case Commands.signaturesUpdated:
 | 
			
		||||
              // do nothing here
 | 
			
		||||
              break;
 | 
			
		||||
 | 
			
		||||
            case Commands.linkSignatureToSystem:
 | 
			
		||||
              // do nothing here
 | 
			
		||||
              break;
 | 
			
		||||
 | 
			
		||||
            default:
 | 
			
		||||
              console.warn(`Map handlers: Unknown command: ${type}`, data);
 | 
			
		||||
              break;
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,36 @@
 | 
			
		||||
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];
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										174
									
								
								assets/js/hooks/Mapper/components/map/utils/changes.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								assets/js/hooks/Mapper/components/map/utils/changes.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,174 @@
 | 
			
		||||
/* 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[];
 | 
			
		||||
}
 | 
			
		||||
@@ -7,7 +7,7 @@ import { Button } from 'primereact/button';
 | 
			
		||||
import { OutCommand } from '@/hooks/Mapper/types';
 | 
			
		||||
import { IconField } from 'primereact/iconfield';
 | 
			
		||||
import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager.ts';
 | 
			
		||||
import { WdImageSize, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
 | 
			
		||||
import { WdImageSize, WdImgButton, TooltipPosition } from '@/hooks/Mapper/components/ui-kit';
 | 
			
		||||
 | 
			
		||||
interface SystemCustomLabelDialog {
 | 
			
		||||
  systemId: string;
 | 
			
		||||
@@ -79,14 +79,14 @@ export const SystemCustomLabelDialog = ({ systemId, visible, setVisible }: Syste
 | 
			
		||||
 | 
			
		||||
  // @ts-ignore
 | 
			
		||||
  const handleInput = useCallback(e => {
 | 
			
		||||
    e.target.value = e.target.value.toUpperCase().replace(/[^A-Z0-9]/g, '');
 | 
			
		||||
    e.target.value = e.target.value.toUpperCase().replace(/[^A-Z0-9\-[\](){}]/g, '');
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Dialog
 | 
			
		||||
      header="Edit label"
 | 
			
		||||
      visible={visible}
 | 
			
		||||
      draggable={false}
 | 
			
		||||
      draggable={true}
 | 
			
		||||
      style={{ width: '250px' }}
 | 
			
		||||
      onHide={onHide}
 | 
			
		||||
      onShow={onShow}
 | 
			
		||||
@@ -100,9 +100,13 @@ export const SystemCustomLabelDialog = ({ systemId, visible, setVisible }: Syste
 | 
			
		||||
              <IconField>
 | 
			
		||||
                {label !== '' && (
 | 
			
		||||
                  <WdImgButton
 | 
			
		||||
                    className="pi pi-trash p-input-icon"
 | 
			
		||||
                    className="pi pi-trash text-red-400"
 | 
			
		||||
                    textSize={WdImageSize.large}
 | 
			
		||||
                    tooltip={{ content: 'Reset label' }}
 | 
			
		||||
                    tooltip={{
 | 
			
		||||
                      content: 'Remove custom label',
 | 
			
		||||
                      className: 'pi p-input-icon',
 | 
			
		||||
                      position: TooltipPosition.top,
 | 
			
		||||
                    }}
 | 
			
		||||
                    onClick={handleReset}
 | 
			
		||||
                  />
 | 
			
		||||
                )}
 | 
			
		||||
@@ -111,7 +115,7 @@ export const SystemCustomLabelDialog = ({ systemId, visible, setVisible }: Syste
 | 
			
		||||
                  aria-describedby="username-help"
 | 
			
		||||
                  autoComplete="off"
 | 
			
		||||
                  value={label}
 | 
			
		||||
                  maxLength={3}
 | 
			
		||||
                  maxLength={5}
 | 
			
		||||
                  onChange={e => setLabel(e.target.value)}
 | 
			
		||||
                  // @ts-expect-error
 | 
			
		||||
                  ref={inputRef}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,73 @@
 | 
			
		||||
import { useCallback, useRef } from 'react';
 | 
			
		||||
import { Dialog } from 'primereact/dialog';
 | 
			
		||||
 | 
			
		||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
 | 
			
		||||
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 {
 | 
			
		||||
  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 },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignatureDialogProps) => {
 | 
			
		||||
  const { outCommand } = useMapRootState();
 | 
			
		||||
 | 
			
		||||
  const ref = useRef({ outCommand });
 | 
			
		||||
  ref.current = { outCommand };
 | 
			
		||||
 | 
			
		||||
  const handleHide = useCallback(() => {
 | 
			
		||||
    setVisible(false);
 | 
			
		||||
  }, [setVisible]);
 | 
			
		||||
 | 
			
		||||
  const handleSelect = useCallback(
 | 
			
		||||
    (signature: SystemSignature) => {
 | 
			
		||||
      if (!signature) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const { outCommand } = ref.current;
 | 
			
		||||
 | 
			
		||||
      outCommand({
 | 
			
		||||
        type: OutCommand.linkSignatureToSystem,
 | 
			
		||||
        data: {
 | 
			
		||||
          ...data,
 | 
			
		||||
          signature_eve_id: signature.eve_id,
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
      setVisible(false);
 | 
			
		||||
    },
 | 
			
		||||
    [data, setVisible],
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Dialog
 | 
			
		||||
      header="Select signature to link"
 | 
			
		||||
      visible
 | 
			
		||||
      draggable={false}
 | 
			
		||||
      style={{ width: '500px' }}
 | 
			
		||||
      onHide={handleHide}
 | 
			
		||||
      contentClassName="!p-0"
 | 
			
		||||
    >
 | 
			
		||||
      <SystemSignaturesContent
 | 
			
		||||
        systemId={`${data.solar_system_source}`}
 | 
			
		||||
        hideLinkedSignatures
 | 
			
		||||
        settings={signatureSettings}
 | 
			
		||||
        onSelect={handleSelect}
 | 
			
		||||
        selectable={true}
 | 
			
		||||
      />
 | 
			
		||||
    </Dialog>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
export * from './SystemLinkSignatureDialog';
 | 
			
		||||
@@ -90,7 +90,7 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const handleInput = useCallback((e: any) => {
 | 
			
		||||
    e.target.value = e.target.value.toUpperCase().replace(/[^A-Z0-9]/g, '');
 | 
			
		||||
    e.target.value = e.target.value.toUpperCase().replace(/[^A-Z0-9\-[\](){}]/g, '');
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
@@ -160,7 +160,7 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
 | 
			
		||||
                  aria-describedby="label"
 | 
			
		||||
                  autoComplete="off"
 | 
			
		||||
                  value={label}
 | 
			
		||||
                  maxLength={3}
 | 
			
		||||
                  maxLength={5}
 | 
			
		||||
                  onChange={e => setLabel(e.target.value)}
 | 
			
		||||
                  onInput={handleInput}
 | 
			
		||||
                />
 | 
			
		||||
 
 | 
			
		||||
@@ -2,3 +2,4 @@ export * from './Widget';
 | 
			
		||||
export * from './WidgetsGrid';
 | 
			
		||||
export * from './SystemSettingsDialog';
 | 
			
		||||
export * from './SystemCustomLabelDialog';
 | 
			
		||||
export * from './SystemLinkSignatureDialog';
 | 
			
		||||
 
 | 
			
		||||
@@ -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 { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
 | 
			
		||||
import { emitMapEvent } from '@/hooks/Mapper/events';
 | 
			
		||||
 | 
			
		||||
export type RouteSystemProps = {
 | 
			
		||||
  destination: number;
 | 
			
		||||
@@ -88,11 +88,10 @@ export interface RoutesListProps {
 | 
			
		||||
 | 
			
		||||
export const RoutesList = ({ data, onContextMenu }: RoutesListProps) => {
 | 
			
		||||
  const [selected, setSelected] = useState<number | null>(null);
 | 
			
		||||
  const { mapRef } = useMapRootState();
 | 
			
		||||
 | 
			
		||||
  const handleClick = useCallback(
 | 
			
		||||
    (systemId: number) => mapRef.current?.command(Commands.selectSystem, systemId.toString()),
 | 
			
		||||
    [mapRef],
 | 
			
		||||
    (systemId: number) => emitMapEvent({ name: Commands.centerSystem, data: systemId?.toString() }),
 | 
			
		||||
    [],
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  if (!data.has_connection) {
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,6 @@ const sortByDist = (a: Route, b: Route) => {
 | 
			
		||||
export const RoutesWidgetContent = () => {
 | 
			
		||||
  const {
 | 
			
		||||
    data: { selectedSystems, hubs = [], systems, routes },
 | 
			
		||||
    mapRef,
 | 
			
		||||
    outCommand,
 | 
			
		||||
  } = useMapRootState();
 | 
			
		||||
 | 
			
		||||
@@ -38,11 +37,10 @@ export const RoutesWidgetContent = () => {
 | 
			
		||||
 | 
			
		||||
  const { loading } = useLoadRoutes();
 | 
			
		||||
 | 
			
		||||
  const { systems: systemStatics, loadSystems } = useLoadSystemStatic({ systems: hubs ?? [] });
 | 
			
		||||
  const { systems: systemStatics, loadSystems, lastUpdateKey } = useLoadSystemStatic({ systems: hubs ?? [] });
 | 
			
		||||
  const { open, ...systemCtxProps } = useContextMenuSystemInfoHandlers({
 | 
			
		||||
    outCommand,
 | 
			
		||||
    hubs,
 | 
			
		||||
    mapRef,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const preparedHubs = useMemo(() => {
 | 
			
		||||
@@ -51,9 +49,10 @@ export const RoutesWidgetContent = () => {
 | 
			
		||||
 | 
			
		||||
      return { ...systemStatics.get(parseInt(x))!, ...(sys && { customName: sys.name ?? '' }) };
 | 
			
		||||
    });
 | 
			
		||||
  }, [hubs, systems, systemStatics]);
 | 
			
		||||
    // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
			
		||||
  }, [hubs, systems, systemStatics, lastUpdateKey]);
 | 
			
		||||
 | 
			
		||||
  const preparedRoutes = useMemo(() => {
 | 
			
		||||
  const preparedRoutes: Route[] = useMemo(() => {
 | 
			
		||||
    return (
 | 
			
		||||
      routes?.routes
 | 
			
		||||
        .sort(sortByDist)
 | 
			
		||||
@@ -70,15 +69,17 @@ export const RoutesWidgetContent = () => {
 | 
			
		||||
    );
 | 
			
		||||
  }, [routes?.routes, routes?.systems_static_data, systemId]);
 | 
			
		||||
 | 
			
		||||
  const refData = useRef({ open, loadSystems });
 | 
			
		||||
  refData.current = { open, loadSystems };
 | 
			
		||||
  const refData = useRef({ open, loadSystems, preparedRoutes });
 | 
			
		||||
  refData.current = { open, loadSystems, preparedRoutes };
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    (async () => await refData.current.loadSystems(hubs))();
 | 
			
		||||
  }, [hubs]);
 | 
			
		||||
 | 
			
		||||
  const handleClick = useCallback((e: MouseEvent, systemId: string) => {
 | 
			
		||||
    refData.current.open(e, systemId);
 | 
			
		||||
    const route = refData.current.preparedRoutes.find(x => x.destination.toString() === systemId);
 | 
			
		||||
 | 
			
		||||
    refData.current.open(e, systemId, route?.mapped_systems ?? []);
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const handleContextMenu = useCallback(
 | 
			
		||||
@@ -114,6 +115,10 @@ export const RoutesWidgetContent = () => {
 | 
			
		||||
          {preparedRoutes.map(route => {
 | 
			
		||||
            const sys = preparedHubs.find(x => x.solar_system_id === route.destination)!;
 | 
			
		||||
 | 
			
		||||
            // TODO do not delte this console log
 | 
			
		||||
            // eslint-disable-next-line no-console
 | 
			
		||||
            // console.log('JOipP', `Check sys [${route.destination}]:`, sys);
 | 
			
		||||
 | 
			
		||||
            return (
 | 
			
		||||
              <>
 | 
			
		||||
                <div className="flex gap-2 items-center">
 | 
			
		||||
@@ -141,7 +146,14 @@ export const RoutesWidgetContent = () => {
 | 
			
		||||
        </div>
 | 
			
		||||
      )}
 | 
			
		||||
 | 
			
		||||
      <ContextMenuSystemInfo hubs={hubs} systems={systems} systemStatics={systemStatics} {...systemCtxProps} />
 | 
			
		||||
      <ContextMenuSystemInfo
 | 
			
		||||
        hubs={hubs}
 | 
			
		||||
        routes={preparedRoutes}
 | 
			
		||||
        systems={systems}
 | 
			
		||||
        systemStatics={systemStatics}
 | 
			
		||||
        systemIdFrom={systemId}
 | 
			
		||||
        {...systemCtxProps}
 | 
			
		||||
      />
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -13,9 +13,10 @@ export const SystemInfoContent = ({ systemId }: SystemInfoContentProps) => {
 | 
			
		||||
    data: { systems, wormholesData },
 | 
			
		||||
  } = useMapRootState();
 | 
			
		||||
 | 
			
		||||
  const sys = getSystemById(systems, systemId)!;
 | 
			
		||||
  const sys = getSystemById(systems, systemId)! || {};
 | 
			
		||||
  const { description } = sys;
 | 
			
		||||
  const { system_class, region_name, constellation_name, statics, effect_name, effect_power } = sys.system_static_info;
 | 
			
		||||
  const { system_class, region_name, constellation_name, statics, effect_name, effect_power } =
 | 
			
		||||
    sys.system_static_info || {};
 | 
			
		||||
  const isWH = isWormholeSpace(system_class);
 | 
			
		||||
  const sortedStatics = useMemo(() => sortWHClasses(wormholesData, statics), [wormholesData, statics]);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,79 @@
 | 
			
		||||
.verticalTabsContainer {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  min-height: 300px;
 | 
			
		||||
 | 
			
		||||
  :global {
 | 
			
		||||
    .p-tabview {
 | 
			
		||||
      width: 100%;
 | 
			
		||||
      display: flex;
 | 
			
		||||
      align-items: flex-start;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .p-tabview-panels {
 | 
			
		||||
      padding: 6px 1rem !important;
 | 
			
		||||
      flex-grow: 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .p-tabview-nav-container {
 | 
			
		||||
      border-right: none;
 | 
			
		||||
      height: 100%;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .p-tabview-nav {
 | 
			
		||||
      flex-direction: column;
 | 
			
		||||
      width: 150px;
 | 
			
		||||
      min-height: 100%;
 | 
			
		||||
      border: none;
 | 
			
		||||
 | 
			
		||||
      li {
 | 
			
		||||
        width: 100%;
 | 
			
		||||
        border-right: 4px solid var(--surface-hover);
 | 
			
		||||
        background-color: var(--surface-card);
 | 
			
		||||
 | 
			
		||||
        transition:
 | 
			
		||||
          background-color 200ms,
 | 
			
		||||
          border-right-color 200ms;
 | 
			
		||||
 | 
			
		||||
        &:hover {
 | 
			
		||||
          background-color: var(--surface-hover);
 | 
			
		||||
          border-right: 4px solid var(--surface-100);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .p-tabview-nav-link {
 | 
			
		||||
          transition: color 200ms;
 | 
			
		||||
 | 
			
		||||
          justify-content: flex-end;
 | 
			
		||||
          padding: 10px;
 | 
			
		||||
          //background-color: var(--surface-card);
 | 
			
		||||
          background-color: initial;
 | 
			
		||||
          border: none;
 | 
			
		||||
          color: var(--gray-400);
 | 
			
		||||
 | 
			
		||||
          border-radius: initial;
 | 
			
		||||
          font-weight: 400;
 | 
			
		||||
          margin: 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        &.p-tabview-selected {
 | 
			
		||||
          background-color: var(--surface-50);
 | 
			
		||||
          border-right: 4px solid var(--primary-color);
 | 
			
		||||
 | 
			
		||||
          .p-tabview-nav-link {
 | 
			
		||||
            font-weight: 600;
 | 
			
		||||
            color: var(--primary-color);
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          &:hover {
 | 
			
		||||
            //background-color: var(--surface-hover);
 | 
			
		||||
            border-right: 4px solid var(--primary-color);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .p-tabview-panel {
 | 
			
		||||
      flex-grow: 1;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -2,8 +2,18 @@ import { Dialog } from 'primereact/dialog';
 | 
			
		||||
import { useCallback, useState } from 'react';
 | 
			
		||||
import { Button } from 'primereact/button';
 | 
			
		||||
import { Checkbox } from 'primereact/checkbox';
 | 
			
		||||
import { TabPanel, TabView } from 'primereact/tabview';
 | 
			
		||||
import styles from './SystemSignatureSettingsDialog.module.scss';
 | 
			
		||||
 | 
			
		||||
export type Setting = { key: string; name: string; value: boolean };
 | 
			
		||||
export type Setting = { key: string; name: string; value: boolean; isFilter?: boolean };
 | 
			
		||||
 | 
			
		||||
export const COSMIC_SIGNATURE = 'Cosmic Signature';
 | 
			
		||||
export const COSMIC_ANOMALY = 'Cosmic Anomaly';
 | 
			
		||||
export const DEPLOYABLE = 'Deployable';
 | 
			
		||||
export const STRUCTURE = 'Structure';
 | 
			
		||||
export const STARBASE = 'Starbase';
 | 
			
		||||
export const SHIP = 'Ship';
 | 
			
		||||
export const DRONE = 'Drone';
 | 
			
		||||
 | 
			
		||||
interface SystemSignatureSettingsDialogProps {
 | 
			
		||||
  settings: Setting[];
 | 
			
		||||
@@ -16,8 +26,12 @@ export const SystemSignatureSettingsDialog = ({
 | 
			
		||||
  onSave,
 | 
			
		||||
  onCancel,
 | 
			
		||||
}: SystemSignatureSettingsDialogProps) => {
 | 
			
		||||
  const [activeIndex, setActiveIndex] = useState(0);
 | 
			
		||||
  const [settings, setSettings] = useState<Setting[]>(defaultSettings);
 | 
			
		||||
 | 
			
		||||
  const filterSettings = settings.filter(setting => setting.isFilter);
 | 
			
		||||
  const userSettings = settings.filter(setting => !setting.isFilter);
 | 
			
		||||
 | 
			
		||||
  const handleSettingsChange = (key: string) => {
 | 
			
		||||
    setSettings(prevState => prevState.map(item => (item.key === key ? { ...item, value: !item.value } : item)));
 | 
			
		||||
  };
 | 
			
		||||
@@ -27,23 +41,53 @@ export const SystemSignatureSettingsDialog = ({
 | 
			
		||||
  }, [onSave, settings]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Dialog header="Filter signatures" visible draggable={false} style={{ width: '300px' }} onHide={onCancel}>
 | 
			
		||||
    <Dialog header="System Signatures Settings" visible={true} onHide={onCancel} className="w-full max-w-lg">
 | 
			
		||||
      <div className="flex flex-col gap-3">
 | 
			
		||||
        <div className="flex flex-col gap-2">
 | 
			
		||||
          {settings.map(setting => {
 | 
			
		||||
            return (
 | 
			
		||||
              <div key={setting.key} className="flex items-center">
 | 
			
		||||
                <Checkbox
 | 
			
		||||
                  inputId={setting.key}
 | 
			
		||||
                  checked={setting.value}
 | 
			
		||||
                  onChange={() => handleSettingsChange(setting.key)}
 | 
			
		||||
                />
 | 
			
		||||
                <label htmlFor={setting.key} className="ml-2">
 | 
			
		||||
                  {setting.name}
 | 
			
		||||
                </label>
 | 
			
		||||
              </div>
 | 
			
		||||
            );
 | 
			
		||||
          })}
 | 
			
		||||
          <div className={styles.verticalTabsContainer}>
 | 
			
		||||
            <TabView
 | 
			
		||||
              activeIndex={activeIndex}
 | 
			
		||||
              onTabChange={e => setActiveIndex(e.index)}
 | 
			
		||||
              className={styles.verticalTabView}
 | 
			
		||||
            >
 | 
			
		||||
              <TabPanel header="Filters" headerClassName={styles.verticalTabHeader}>
 | 
			
		||||
                <div className="w-full h-full flex flex-col gap-1">
 | 
			
		||||
                  {filterSettings.map(setting => {
 | 
			
		||||
                    return (
 | 
			
		||||
                      <div key={setting.key} className="flex items-center">
 | 
			
		||||
                        <Checkbox
 | 
			
		||||
                          inputId={setting.key}
 | 
			
		||||
                          checked={setting.value}
 | 
			
		||||
                          onChange={() => handleSettingsChange(setting.key)}
 | 
			
		||||
                        />
 | 
			
		||||
                        <label htmlFor={setting.key} className="ml-2">
 | 
			
		||||
                          {setting.name}
 | 
			
		||||
                        </label>
 | 
			
		||||
                      </div>
 | 
			
		||||
                    );
 | 
			
		||||
                  })}
 | 
			
		||||
                </div>
 | 
			
		||||
              </TabPanel>
 | 
			
		||||
              <TabPanel header="User Interface" headerClassName={styles.verticalTabHeader}>
 | 
			
		||||
                <div className="w-full h-full flex flex-col gap-1">
 | 
			
		||||
                  {userSettings.map(setting => {
 | 
			
		||||
                    return (
 | 
			
		||||
                      <div key={setting.key} className="flex items-center">
 | 
			
		||||
                        <Checkbox
 | 
			
		||||
                          inputId={setting.key}
 | 
			
		||||
                          checked={setting.value}
 | 
			
		||||
                          onChange={() => handleSettingsChange(setting.key)}
 | 
			
		||||
                        />
 | 
			
		||||
                        <label htmlFor={setting.key} className="ml-2">
 | 
			
		||||
                          {setting.name}
 | 
			
		||||
                        </label>
 | 
			
		||||
                      </div>
 | 
			
		||||
                    );
 | 
			
		||||
                  })}
 | 
			
		||||
                </div>
 | 
			
		||||
              </TabPanel>
 | 
			
		||||
            </TabView>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div className="flex gap-2 justify-end">
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,18 @@
 | 
			
		||||
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
 | 
			
		||||
import { InfoDrawer, LayoutEventBlocker, TooltipPosition, WdImgButton } from '@/hooks/Mapper/components/ui-kit';
 | 
			
		||||
import { SystemSignaturesContent } from './SystemSignaturesContent';
 | 
			
		||||
import { Setting, SystemSignatureSettingsDialog } from './SystemSignatureSettingsDialog';
 | 
			
		||||
import {
 | 
			
		||||
  Setting,
 | 
			
		||||
  SystemSignatureSettingsDialog,
 | 
			
		||||
  COSMIC_SIGNATURE,
 | 
			
		||||
  COSMIC_ANOMALY,
 | 
			
		||||
  DEPLOYABLE,
 | 
			
		||||
  STRUCTURE,
 | 
			
		||||
  STARBASE,
 | 
			
		||||
  SHIP,
 | 
			
		||||
  DRONE,
 | 
			
		||||
} from './SystemSignatureSettingsDialog';
 | 
			
		||||
import { SignatureGroup } from '@/hooks/Mapper/types';
 | 
			
		||||
 | 
			
		||||
import React, { useCallback, useEffect, useState } from 'react';
 | 
			
		||||
 | 
			
		||||
@@ -9,26 +20,28 @@ import { PrimeIcons } from 'primereact/api';
 | 
			
		||||
 | 
			
		||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
 | 
			
		||||
 | 
			
		||||
export const COSMIC_SIGNATURE = 'Cosmic Signature';
 | 
			
		||||
export const COSMIC_ANOMALY = 'Cosmic Anomaly';
 | 
			
		||||
export const DEPLOYABLE = 'Deployable';
 | 
			
		||||
export const STRUCTURE = 'Structure';
 | 
			
		||||
export const STARBASE = 'Starbase';
 | 
			
		||||
export const SHIP = 'Ship';
 | 
			
		||||
export const DRONE = 'Drone';
 | 
			
		||||
const SIGNATURE_SETTINGS_KEY = 'wanderer_system_signature_settings_v4_1';
 | 
			
		||||
export const SHOW_DESCRIPTION_COLUMN_SETTING = 'show_description_column_setting';
 | 
			
		||||
export const SHOW_INSERTED_COLUMN_SETTING = 'show_inserted_column_setting';
 | 
			
		||||
 | 
			
		||||
const settings: Setting[] = [
 | 
			
		||||
  { key: COSMIC_ANOMALY, name: 'Show Anomalies', value: true },
 | 
			
		||||
  { key: COSMIC_SIGNATURE, name: 'Show Cosmic Signatures', value: true },
 | 
			
		||||
  { key: DEPLOYABLE, name: 'Show Deployables', value: true },
 | 
			
		||||
  { key: STRUCTURE, name: 'Show Structures', value: true },
 | 
			
		||||
  { key: STARBASE, name: 'Show Starbase', value: true },
 | 
			
		||||
  { key: SHIP, name: 'Show Ships', value: true },
 | 
			
		||||
  { key: DRONE, name: 'Show Drones And Charges', value: true },
 | 
			
		||||
  { key: SHOW_INSERTED_COLUMN_SETTING, name: 'Show Inserted Column', value: false, isFilter: false },
 | 
			
		||||
  { key: SHOW_DESCRIPTION_COLUMN_SETTING, name: 'Show Description Column', value: false, isFilter: false },
 | 
			
		||||
  { key: COSMIC_ANOMALY, name: 'Show Anomalies', value: true, isFilter: true },
 | 
			
		||||
  { key: COSMIC_SIGNATURE, name: 'Show Cosmic Signatures', value: true, isFilter: true },
 | 
			
		||||
  { key: DEPLOYABLE, name: 'Show Deployables', value: true, isFilter: true },
 | 
			
		||||
  { key: STRUCTURE, name: 'Show Structures', value: true, isFilter: true },
 | 
			
		||||
  { key: STARBASE, name: 'Show Starbase', value: true, isFilter: true },
 | 
			
		||||
  { key: SHIP, name: 'Show Ships', value: true, isFilter: true },
 | 
			
		||||
  { key: DRONE, name: 'Show Drones And Charges', value: true, isFilter: true },
 | 
			
		||||
  { key: SignatureGroup.Wormhole, name: 'Show Wormholes', value: true, isFilter: true },
 | 
			
		||||
  { key: SignatureGroup.RelicSite, name: 'Show Relic Sites', value: true, isFilter: true },
 | 
			
		||||
  { key: SignatureGroup.DataSite, name: 'Show Data Sites', value: true, isFilter: true },
 | 
			
		||||
  { key: SignatureGroup.OreSite, name: 'Show Ore Sites', value: true, isFilter: true },
 | 
			
		||||
  { key: SignatureGroup.GasSite, name: 'Show Gas Sites', value: true, isFilter: true },
 | 
			
		||||
  { key: SignatureGroup.CombatSite, name: 'Show Combat Sites', value: true, isFilter: true },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const SIGNATURE_SETTINGS_KEY = 'wanderer_system_signature_settings';
 | 
			
		||||
 | 
			
		||||
const defaultSettings = () => {
 | 
			
		||||
  return [...settings];
 | 
			
		||||
};
 | 
			
		||||
@@ -89,8 +102,7 @@ export const SystemSignatures = () => {
 | 
			
		||||
                    </InfoDrawer>
 | 
			
		||||
                    <InfoDrawer title={<b className="text-slate-50">How to delete?</b>}>
 | 
			
		||||
                      For delete any signature first of all you need select before
 | 
			
		||||
                      <br /> and then use <b className="text-sky-500">Del</b> or{' '}
 | 
			
		||||
                      <b className="text-sky-500">Backspace</b>
 | 
			
		||||
                      <br /> and then use <b className="text-sky-500">Backspace</b>
 | 
			
		||||
                    </InfoDrawer>
 | 
			
		||||
                  </div>
 | 
			
		||||
                ) as React.ReactNode,
 | 
			
		||||
 
 | 
			
		||||
@@ -4,3 +4,7 @@
 | 
			
		||||
  font-size: 12px !important;
 | 
			
		||||
  line-height: 8px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.Table {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,11 @@
 | 
			
		||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
 | 
			
		||||
import { useClipboard } from '@/hooks/Mapper/hooks/useClipboard';
 | 
			
		||||
import { parseSignatures } from '@/hooks/Mapper/helpers';
 | 
			
		||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
 | 
			
		||||
import { Commands, OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
 | 
			
		||||
import { WdTooltip, WdTooltipHandlers } from '@/hooks/Mapper/components/ui-kit';
 | 
			
		||||
import { GROUPS_LIST } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
 | 
			
		||||
 | 
			
		||||
import { DataTable, DataTableRowMouseEvent } from 'primereact/datatable';
 | 
			
		||||
import { DataTable, DataTableRowClickEvent, DataTableRowMouseEvent, SortOrder } from 'primereact/datatable';
 | 
			
		||||
import { Column } from 'primereact/column';
 | 
			
		||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
 | 
			
		||||
import useRefState from 'react-usestateref';
 | 
			
		||||
@@ -21,24 +22,61 @@ import {
 | 
			
		||||
  getRowColorByTimeLeft,
 | 
			
		||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/helpers';
 | 
			
		||||
import {
 | 
			
		||||
  renderDescription,
 | 
			
		||||
  renderIcon,
 | 
			
		||||
  renderName,
 | 
			
		||||
  renderTimeLeft,
 | 
			
		||||
  renderInfoColumn,
 | 
			
		||||
  renderInsertedTimeLeft,
 | 
			
		||||
  renderUpdatedTimeLeft,
 | 
			
		||||
} 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_INSERTED_COLUMN_SETTING,
 | 
			
		||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures';
 | 
			
		||||
type SystemSignaturesSortSettings = {
 | 
			
		||||
  sortField: string;
 | 
			
		||||
  sortOrder: SortOrder;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const SORT_DEFAULT_VALUES: SystemSignaturesSortSettings = {
 | 
			
		||||
  sortField: 'updated_at',
 | 
			
		||||
  sortOrder: -1,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface SystemSignaturesContentProps {
 | 
			
		||||
  systemId: string;
 | 
			
		||||
  settings: Setting[];
 | 
			
		||||
  hideLinkedSignatures?: boolean;
 | 
			
		||||
  selectable?: boolean;
 | 
			
		||||
  onSelect?: (signature: SystemSignature) => void;
 | 
			
		||||
}
 | 
			
		||||
export const SystemSignaturesContent = ({ systemId, settings }: SystemSignaturesContentProps) => {
 | 
			
		||||
export const SystemSignaturesContent = ({
 | 
			
		||||
  systemId,
 | 
			
		||||
  settings,
 | 
			
		||||
  hideLinkedSignatures,
 | 
			
		||||
  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);
 | 
			
		||||
 | 
			
		||||
  const [sortSettings, setSortSettings] = useLocalStorageState<SystemSignaturesSortSettings>('window:signatures:sort', {
 | 
			
		||||
    defaultValue: SORT_DEFAULT_VALUES,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const tableRef = useRef<HTMLDivElement>(null);
 | 
			
		||||
  const compact = useMaxWidth(tableRef, 260);
 | 
			
		||||
  const medium = useMaxWidth(tableRef, 380);
 | 
			
		||||
@@ -50,19 +88,47 @@ export const SystemSignaturesContent = ({ systemId, settings }: SystemSignatures
 | 
			
		||||
  const handleResize = useCallback(() => {
 | 
			
		||||
    if (tableRef.current) {
 | 
			
		||||
      const tableWidth = tableRef.current.offsetWidth;
 | 
			
		||||
      const otherColumnsWidth = 265;
 | 
			
		||||
      const otherColumnsWidth = 276;
 | 
			
		||||
      const availableWidth = tableWidth - otherColumnsWidth;
 | 
			
		||||
      setNameColumnWidth(`${availableWidth}px`);
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  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 showInsertedColumn = useMemo(
 | 
			
		||||
    () => settings.find(s => s.key === SHOW_INSERTED_COLUMN_SETTING)?.value,
 | 
			
		||||
    [settings],
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const filteredSignatures = useMemo(() => {
 | 
			
		||||
    return signatures
 | 
			
		||||
      .filter(x => settings.find(y => y.key === x.kind)?.value)
 | 
			
		||||
      .filter(x => {
 | 
			
		||||
        if (hideLinkedSignatures && !!x.linked_system) {
 | 
			
		||||
          return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const isCosmicSignature = x.kind === COSMIC_SIGNATURE;
 | 
			
		||||
 | 
			
		||||
        if (isCosmicSignature) {
 | 
			
		||||
          const showCosmicSignatures = settings.find(y => y.key === COSMIC_SIGNATURE)?.value;
 | 
			
		||||
          if (showCosmicSignatures) {
 | 
			
		||||
            return !x.group || groupSettings.find(y => y.key === x.group)?.value;
 | 
			
		||||
          } else {
 | 
			
		||||
            return !!x.group && groupSettings.find(y => y.key === x.group)?.value;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return settings.find(y => y.key === x.kind)?.value;
 | 
			
		||||
      })
 | 
			
		||||
      .sort((a, b) => {
 | 
			
		||||
        return new Date(b.updated_at || 0).getTime() - new Date(a.updated_at || 0).getTime();
 | 
			
		||||
      });
 | 
			
		||||
  }, [signatures, settings]);
 | 
			
		||||
  }, [signatures, settings, groupSettings, hideLinkedSignatures]);
 | 
			
		||||
 | 
			
		||||
  const handleGetSignatures = useCallback(async () => {
 | 
			
		||||
    const { signatures } = await outCommand({
 | 
			
		||||
@@ -70,12 +136,13 @@ export const SystemSignaturesContent = ({ systemId, settings }: SystemSignatures
 | 
			
		||||
      data: { system_id: systemId },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    setAskUser(false);
 | 
			
		||||
    setSignatures(signatures);
 | 
			
		||||
  }, [outCommand, systemId]);
 | 
			
		||||
 | 
			
		||||
  const handleUpdateSignatures = useCallback(
 | 
			
		||||
    async (newSignatures: SystemSignature[]) => {
 | 
			
		||||
      const { added, updated, removed } = getActualSigs(signaturesRef.current, newSignatures);
 | 
			
		||||
    async (newSignatures: SystemSignature[], updateOnly: boolean) => {
 | 
			
		||||
      const { added, updated, removed } = getActualSigs(signaturesRef.current, newSignatures, updateOnly);
 | 
			
		||||
 | 
			
		||||
      const { signatures: updatedSignatures } = await outCommand({
 | 
			
		||||
        type: OutCommand.updateSignatures,
 | 
			
		||||
@@ -94,43 +161,96 @@ export const SystemSignaturesContent = ({ systemId, settings }: SystemSignatures
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  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)));
 | 
			
		||||
  }, [handleUpdateSignatures, signatures, selectedSignatures]);
 | 
			
		||||
    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
 | 
			
		||||
    e => {
 | 
			
		||||
      if (selectable) {
 | 
			
		||||
        onSelect?.(e.value);
 | 
			
		||||
      } else {
 | 
			
		||||
        setSelectedSignatures(e.value);
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    [onSelect, selectable],
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  useHotkey(true, ['a'], handleSelectAll);
 | 
			
		||||
 | 
			
		||||
  useHotkey(false, ['Backspace', 'Delete'], handleDeleteSelected);
 | 
			
		||||
  useHotkey(false, ['Backspace'], handleDeleteSelected);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (selectable) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!clipboardContent) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const signatures = parseSignatures(
 | 
			
		||||
    const newSignatures = parseSignatures(
 | 
			
		||||
      clipboardContent,
 | 
			
		||||
      settings.map(x => x.key),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    handleUpdateSignatures(signatures);
 | 
			
		||||
  }, [clipboardContent]);
 | 
			
		||||
    const { removed } = getActualSigs(signaturesRef.current, newSignatures, false);
 | 
			
		||||
 | 
			
		||||
    if (!signaturesRef.current || !signaturesRef.current.length || !removed.length) {
 | 
			
		||||
      handleUpdateSignatures(newSignatures, false);
 | 
			
		||||
    } else {
 | 
			
		||||
      setParsedSignatures(newSignatures);
 | 
			
		||||
      setAskUser(true);
 | 
			
		||||
    }
 | 
			
		||||
  }, [clipboardContent, selectable]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!systemId) {
 | 
			
		||||
      setSignatures([]);
 | 
			
		||||
      setAskUser(false);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    handleGetSignatures();
 | 
			
		||||
  }, [systemId]);
 | 
			
		||||
 | 
			
		||||
  useMapEventListener(event => {
 | 
			
		||||
    switch (event.name) {
 | 
			
		||||
      case Commands.signaturesUpdated:
 | 
			
		||||
        if (event.data?.toString() !== systemId.toString()) {
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        handleGetSignatures();
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const observer = new ResizeObserver(handleResize);
 | 
			
		||||
    if (tableRef.current) {
 | 
			
		||||
@@ -159,83 +279,173 @@ export const SystemSignaturesContent = ({ systemId, settings }: SystemSignatures
 | 
			
		||||
    setHoveredSig(null);
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const renderToolbar = (/*row: SystemSignature*/) => {
 | 
			
		||||
    return (
 | 
			
		||||
      <div className="flex justify-end items-center gap-2 mr-[4px]">
 | 
			
		||||
        <WdTooltipWrapper content="To Edit Signature do double click">
 | 
			
		||||
          <span className={clsx(PrimeIcons.PENCIL, 'text-[10px]')}></span>
 | 
			
		||||
        </WdTooltipWrapper>
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const [showSignatureSettings, setShowSignatureSettings] = useState(false);
 | 
			
		||||
 | 
			
		||||
  const handleRowClick = (e: DataTableRowClickEvent) => {
 | 
			
		||||
    setSelectedSignature(e.data as SystemSignature);
 | 
			
		||||
    setShowSignatureSettings(true);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div ref={tableRef} className="h-full">
 | 
			
		||||
      {filteredSignatures.length === 0 ? (
 | 
			
		||||
        <div className="w-full h-full flex justify-center items-center select-none text-stone-400/80 text-sm">
 | 
			
		||||
          No signatures
 | 
			
		||||
        </div>
 | 
			
		||||
      ) : (
 | 
			
		||||
        <>
 | 
			
		||||
          <DataTable
 | 
			
		||||
            value={filteredSignatures}
 | 
			
		||||
            size="small"
 | 
			
		||||
            selectionMode="multiple"
 | 
			
		||||
            selection={selectedSignatures}
 | 
			
		||||
            onSelectionChange={e => setSelectedSignatures(e.value)}
 | 
			
		||||
            dataKey="eve_id"
 | 
			
		||||
            tableClassName="w-full select-none"
 | 
			
		||||
            resizableColumns
 | 
			
		||||
            rowHover
 | 
			
		||||
            selectAll
 | 
			
		||||
            showHeaders={false}
 | 
			
		||||
            onRowMouseEnter={handleEnterRow}
 | 
			
		||||
            onRowMouseLeave={handleLeaveRow}
 | 
			
		||||
            rowClassName={row => {
 | 
			
		||||
              if (selectedSignatures.some(x => x.eve_id === row.eve_id)) {
 | 
			
		||||
                return clsx(classes.TableRowCompact, 'bg-amber-500/50 hover:bg-amber-500/70 transition duration-200');
 | 
			
		||||
              }
 | 
			
		||||
    <>
 | 
			
		||||
      <div ref={tableRef} className={'h-full '}>
 | 
			
		||||
        {filteredSignatures.length === 0 ? (
 | 
			
		||||
          <div className="w-full h-full flex justify-center items-center select-none text-stone-400/80 text-sm">
 | 
			
		||||
            No signatures
 | 
			
		||||
          </div>
 | 
			
		||||
        ) : (
 | 
			
		||||
          <>
 | 
			
		||||
            {/* @ts-ignore */}
 | 
			
		||||
            <DataTable
 | 
			
		||||
              className={classes.Table}
 | 
			
		||||
              value={filteredSignatures}
 | 
			
		||||
              size="small"
 | 
			
		||||
              selectionMode={selectable ? 'single' : 'multiple'}
 | 
			
		||||
              selection={selectedSignatures}
 | 
			
		||||
              metaKeySelection
 | 
			
		||||
              onSelectionChange={handleSelectSignatures}
 | 
			
		||||
              dataKey="eve_id"
 | 
			
		||||
              tableClassName="w-full select-none"
 | 
			
		||||
              resizableColumns={false}
 | 
			
		||||
              onRowDoubleClick={handleRowClick}
 | 
			
		||||
              rowHover
 | 
			
		||||
              selectAll
 | 
			
		||||
              sortField={sortSettings.sortField}
 | 
			
		||||
              sortOrder={sortSettings.sortOrder}
 | 
			
		||||
              onSort={event => setSortSettings(() => ({ sortField: event.sortField, sortOrder: event.sortOrder }))}
 | 
			
		||||
              onRowMouseEnter={compact || medium ? handleEnterRow : undefined}
 | 
			
		||||
              onRowMouseLeave={compact || medium ? handleLeaveRow : undefined}
 | 
			
		||||
              rowClassName={row => {
 | 
			
		||||
                if (selectedSignatures.some(x => x.eve_id === row.eve_id)) {
 | 
			
		||||
                  return clsx(classes.TableRowCompact, 'bg-amber-500/50 hover:bg-amber-500/70 transition duration-200');
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
              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');
 | 
			
		||||
              }
 | 
			
		||||
                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');
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
              return clsx(classes.TableRowCompact, dateClass);
 | 
			
		||||
            }}
 | 
			
		||||
          >
 | 
			
		||||
            <Column
 | 
			
		||||
              bodyClassName="p-0 px-1"
 | 
			
		||||
              field="group"
 | 
			
		||||
              body={renderIcon}
 | 
			
		||||
              style={{ maxWidth: 26, minWidth: 26, width: 26 }}
 | 
			
		||||
            ></Column>
 | 
			
		||||
                return clsx(classes.TableRowCompact, dateClass);
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              <Column
 | 
			
		||||
                bodyClassName="p-0 px-1"
 | 
			
		||||
                field="group"
 | 
			
		||||
                body={x => renderIcon(x)}
 | 
			
		||||
                style={{ maxWidth: 26, minWidth: 26, width: 26, height: 25 }}
 | 
			
		||||
              ></Column>
 | 
			
		||||
 | 
			
		||||
            <Column
 | 
			
		||||
              field="eve_id"
 | 
			
		||||
              header="Id"
 | 
			
		||||
              bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
 | 
			
		||||
              style={{ maxWidth: 72, minWidth: 72, width: 72 }}
 | 
			
		||||
            ></Column>
 | 
			
		||||
            <Column
 | 
			
		||||
              field="group"
 | 
			
		||||
              header="Group"
 | 
			
		||||
              bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
 | 
			
		||||
              hidden={compact}
 | 
			
		||||
            ></Column>
 | 
			
		||||
            <Column
 | 
			
		||||
              field="name"
 | 
			
		||||
              header="Name"
 | 
			
		||||
              bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
 | 
			
		||||
              body={renderName}
 | 
			
		||||
              style={{ maxWidth: nameColumnWidth }}
 | 
			
		||||
              hidden={compact || medium}
 | 
			
		||||
            ></Column>
 | 
			
		||||
            <Column
 | 
			
		||||
              field="updated_at"
 | 
			
		||||
              header="Updated"
 | 
			
		||||
              dataType="date"
 | 
			
		||||
              bodyClassName="w-[80px] text-ellipsis overflow-hidden whitespace-nowrap"
 | 
			
		||||
              body={renderTimeLeft}
 | 
			
		||||
            ></Column>
 | 
			
		||||
          </DataTable>
 | 
			
		||||
        </>
 | 
			
		||||
      )}
 | 
			
		||||
      <WdTooltip
 | 
			
		||||
        className="bg-stone-900/95 text-slate-50"
 | 
			
		||||
        ref={tooltipRef}
 | 
			
		||||
        content={hoveredSig ? <SignatureView {...hoveredSig} /> : null}
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
              <Column
 | 
			
		||||
                field="eve_id"
 | 
			
		||||
                header="Id"
 | 
			
		||||
                bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
 | 
			
		||||
                style={{ maxWidth: 72, minWidth: 72, width: 72 }}
 | 
			
		||||
                sortable
 | 
			
		||||
              ></Column>
 | 
			
		||||
              <Column
 | 
			
		||||
                field="group"
 | 
			
		||||
                header="Group"
 | 
			
		||||
                bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
 | 
			
		||||
                hidden={compact}
 | 
			
		||||
                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>
 | 
			
		||||
              )}
 | 
			
		||||
 | 
			
		||||
              {showInsertedColumn && (
 | 
			
		||||
                <Column
 | 
			
		||||
                  field="inserted_at"
 | 
			
		||||
                  header="Inserted"
 | 
			
		||||
                  dataType="date"
 | 
			
		||||
                  bodyClassName="w-[70px] text-ellipsis overflow-hidden whitespace-nowrap"
 | 
			
		||||
                  body={renderInsertedTimeLeft}
 | 
			
		||||
                  sortable
 | 
			
		||||
                ></Column>
 | 
			
		||||
              )}
 | 
			
		||||
 | 
			
		||||
              <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>
 | 
			
		||||
              )}
 | 
			
		||||
            </DataTable>
 | 
			
		||||
          </>
 | 
			
		||||
        )}
 | 
			
		||||
        <WdTooltip
 | 
			
		||||
          className="bg-stone-900/95 text-slate-50"
 | 
			
		||||
          ref={tooltipRef}
 | 
			
		||||
          content={hoveredSig ? <SignatureView {...hoveredSig} /> : null}
 | 
			
		||||
        />
 | 
			
		||||
 | 
			
		||||
        {showSignatureSettings && (
 | 
			
		||||
          <SignatureSettings
 | 
			
		||||
            systemId={systemId}
 | 
			
		||||
            show
 | 
			
		||||
            onHide={() => setShowSignatureSettings(false)}
 | 
			
		||||
            signatureData={selectedSignature}
 | 
			
		||||
          />
 | 
			
		||||
        )}
 | 
			
		||||
 | 
			
		||||
        {askUser && (
 | 
			
		||||
          <div className="absolute left-[1px] top-[29px] h-[calc(100%-30px)] w-[calc(100%-3px)] bg-stone-900/10 backdrop-blur-sm">
 | 
			
		||||
            <div className="absolute top-0 left-0 w-full h-full flex flex-col items-center justify-center">
 | 
			
		||||
              <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>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ import { getState } from './getState.ts';
 | 
			
		||||
export const getActualSigs = (
 | 
			
		||||
  oldSignatures: SystemSignature[],
 | 
			
		||||
  newSignatures: SystemSignature[],
 | 
			
		||||
  updateOnly: boolean,
 | 
			
		||||
): { added: SystemSignature[]; updated: SystemSignature[]; removed: SystemSignature[] } => {
 | 
			
		||||
  const updated: SystemSignature[] = [];
 | 
			
		||||
  const removed: SystemSignature[] = [];
 | 
			
		||||
@@ -18,9 +19,13 @@ 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 {
 | 
			
		||||
        updated.push({ ...oldSig });
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      removed.push(oldSig);
 | 
			
		||||
      if (!updateOnly) {
 | 
			
		||||
        removed.push(oldSig);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,9 +7,9 @@ export const getState = (_: string[], newSig: SystemSignature) => {
 | 
			
		||||
  let state = -1;
 | 
			
		||||
  if (!newSig.group || newSig.group === '') {
 | 
			
		||||
    state = 0;
 | 
			
		||||
  } else if (!!newSig.group && newSig.group !== '' && newSig.name === '') {
 | 
			
		||||
  } else if (!newSig.name || newSig.name === '') {
 | 
			
		||||
    state = 1;
 | 
			
		||||
  } else if (!!newSig.group && newSig.group !== '' && newSig.name !== '') {
 | 
			
		||||
  } else if (newSig.name !== '') {
 | 
			
		||||
    state = 2;
 | 
			
		||||
  }
 | 
			
		||||
  return state;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,7 @@
 | 
			
		||||
export * from './renderIcon';
 | 
			
		||||
export * from './renderDescription';
 | 
			
		||||
export * from './renderName';
 | 
			
		||||
export * from './renderTimeLeft';
 | 
			
		||||
export * from './renderInsertedTimeLeft';
 | 
			
		||||
export * from './renderUpdatedTimeLeft';
 | 
			
		||||
export * from './renderLinkedSystem';
 | 
			
		||||
export * from './renderInfoColumn';
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,5 @@
 | 
			
		||||
import { SystemSignature } from '@/hooks/Mapper/types';
 | 
			
		||||
 | 
			
		||||
export const renderDescription = (row: SystemSignature) => {
 | 
			
		||||
  return <span title={row?.description}>{row?.description}</span>;
 | 
			
		||||
};
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
 | 
			
		||||
import { GroupType, SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
 | 
			
		||||
import { GROUPS } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
 | 
			
		||||
 | 
			
		||||
export const renderIcon = (row: SystemSignature) => {
 | 
			
		||||
export const renderIcon = (row: SystemSignature, customSize?: Omit<GroupType, 'icon' | 'id'>) => {
 | 
			
		||||
  if (row.group == null) {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
@@ -13,7 +13,7 @@ export const renderIcon = (row: SystemSignature) => {
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex justify-center items-center">
 | 
			
		||||
      <img src={group.icon} style={{ width: group.w, height: group.h }} />
 | 
			
		||||
      <img src={group.icon} style={{ width: customSize?.w ?? group.w, height: customSize?.h ?? group.h }} />
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,3 @@
 | 
			
		||||
.whFontSize {
 | 
			
		||||
  font-size: 11px !important;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,58 @@
 | 
			
		||||
import { PrimeIcons } from 'primereact/api';
 | 
			
		||||
import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
 | 
			
		||||
import { SystemViewStandalone, WHClassView } from '@/hooks/Mapper/components/ui-kit';
 | 
			
		||||
 | 
			
		||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
 | 
			
		||||
 | 
			
		||||
import clsx from 'clsx';
 | 
			
		||||
import { renderName } from './renderName.tsx';
 | 
			
		||||
import classes from './renderInfoColumn.module.scss';
 | 
			
		||||
 | 
			
		||||
export const renderInfoColumn = (row: SystemSignature) => {
 | 
			
		||||
  if (!row.group || row.group === SignatureGroup.Wormhole) {
 | 
			
		||||
    return (
 | 
			
		||||
      <div className="flex justify-start items-center gap-[6px]">
 | 
			
		||||
        {row.type && (
 | 
			
		||||
          <WHClassView
 | 
			
		||||
            className="text-[11px]"
 | 
			
		||||
            classNameWh={classes.whFontSize}
 | 
			
		||||
            highlightName
 | 
			
		||||
            hideWhClass={!!row.linked_system}
 | 
			
		||||
            whClassName={row.type}
 | 
			
		||||
            noOffset
 | 
			
		||||
            useShortTitle
 | 
			
		||||
          />
 | 
			
		||||
        )}
 | 
			
		||||
 | 
			
		||||
        {row.linked_system && (
 | 
			
		||||
          <>
 | 
			
		||||
            {/*<span className="w-4 h-4 hero-arrow-long-right"></span>*/}
 | 
			
		||||
            <span title={row.linked_system?.solar_system_name}>
 | 
			
		||||
              <SystemViewStandalone
 | 
			
		||||
                className={clsx('select-none text-center cursor-context-menu')}
 | 
			
		||||
                hideRegion
 | 
			
		||||
                {...row.linked_system}
 | 
			
		||||
              />
 | 
			
		||||
            </span>
 | 
			
		||||
          </>
 | 
			
		||||
        )}
 | 
			
		||||
        {row.description && (
 | 
			
		||||
          <WdTooltipWrapper content={row.description}>
 | 
			
		||||
            <span className={clsx(PrimeIcons.EXCLAMATION_CIRCLE, 'text-[12px]')}></span>
 | 
			
		||||
          </WdTooltipWrapper>
 | 
			
		||||
        )}
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex gap-1 items-center">
 | 
			
		||||
      {renderName(row)}{' '}
 | 
			
		||||
      {row.description && (
 | 
			
		||||
        <WdTooltipWrapper content={row.description}>
 | 
			
		||||
          <span className={clsx(PrimeIcons.EXCLAMATION_CIRCLE, 'text-[12px]')}></span>
 | 
			
		||||
        </WdTooltipWrapper>
 | 
			
		||||
      )}
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
@@ -0,0 +1,10 @@
 | 
			
		||||
import { SystemSignature } from '@/hooks/Mapper/types';
 | 
			
		||||
import { TimeLeft } from '@/hooks/Mapper/components/ui-kit';
 | 
			
		||||
 | 
			
		||||
export const renderInsertedTimeLeft = (row: SystemSignature) => {
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex w-full items-center">
 | 
			
		||||
      <TimeLeft cDate={row.inserted_at ? new Date(row.inserted_at) : undefined} />
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
@@ -0,0 +1,20 @@
 | 
			
		||||
import clsx from 'clsx';
 | 
			
		||||
 | 
			
		||||
import { SystemSignature } from '@/hooks/Mapper/types';
 | 
			
		||||
import { SystemViewStandalone } from '@/hooks/Mapper/components/ui-kit';
 | 
			
		||||
 | 
			
		||||
export const renderLinkedSystem = (row: SystemSignature) => {
 | 
			
		||||
  if (!row.linked_system) {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <span title={row.linked_system?.solar_system_name}>
 | 
			
		||||
      <SystemViewStandalone
 | 
			
		||||
        className={clsx('select-none text-center cursor-context-menu')}
 | 
			
		||||
        hideRegion
 | 
			
		||||
        {...row.linked_system}
 | 
			
		||||
      />
 | 
			
		||||
    </span>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
import { SystemSignature } from '@/hooks/Mapper/types';
 | 
			
		||||
import { TimeLeft } from '@/hooks/Mapper/components/ui-kit';
 | 
			
		||||
 | 
			
		||||
export const renderTimeLeft = (row: SystemSignature) => {
 | 
			
		||||
export const renderUpdatedTimeLeft = (row: SystemSignature) => {
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex justify-end w-full items-center">
 | 
			
		||||
    <div className="flex w-full items-center">
 | 
			
		||||
      <TimeLeft cDate={row.updated_at ? new Date(row.updated_at) : undefined} />
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
@@ -6,21 +6,27 @@ import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
 | 
			
		||||
import { useCallback, useState } from 'react';
 | 
			
		||||
import { OnTheMap, RightBar } from '@/hooks/Mapper/components/mapRootContent/components';
 | 
			
		||||
import { MapContextMenu } from '@/hooks/Mapper/components/mapRootContent/components/MapContextMenu/MapContextMenu.tsx';
 | 
			
		||||
import { useSkipContextMenu } from '@/hooks/Mapper/hooks/useSkipContextMenu';
 | 
			
		||||
import { MapSettings } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings';
 | 
			
		||||
 | 
			
		||||
export interface MapRootContentProps {}
 | 
			
		||||
 | 
			
		||||
// eslint-disable-next-line no-empty-pattern
 | 
			
		||||
export const MapRootContent = ({}: MapRootContentProps) => {
 | 
			
		||||
  const { mapRef, interfaceSettings } = useMapRootState();
 | 
			
		||||
  const { interfaceSettings } = useMapRootState();
 | 
			
		||||
  const { isShowMenu } = interfaceSettings;
 | 
			
		||||
 | 
			
		||||
  const [showOnTheMap, setShowOnTheMap] = useState(false);
 | 
			
		||||
  const [showMapSettings, setShowMapSettings] = useState(false);
 | 
			
		||||
  const mapInterface = <MapInterface />;
 | 
			
		||||
 | 
			
		||||
  const handleShowOnTheMap = useCallback(() => setShowOnTheMap(true), []);
 | 
			
		||||
  const handleShowMapSettings = useCallback(() => setShowMapSettings(true), []);
 | 
			
		||||
 | 
			
		||||
  useSkipContextMenu();
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Layout map={<MapWrapper refn={mapRef} />}>
 | 
			
		||||
    <Layout map={<MapWrapper />}>
 | 
			
		||||
      {!isShowMenu ? (
 | 
			
		||||
        <div className="absolute top-0 left-14 w-[calc(100%-3.5rem)] h-[calc(100%-3.5rem)] pointer-events-none">
 | 
			
		||||
          <div className="absolute top-0 left-0 w-[calc(100%-3.5rem)] h-full pointer-events-none">
 | 
			
		||||
@@ -28,18 +34,19 @@ export const MapRootContent = ({}: MapRootContentProps) => {
 | 
			
		||||
            {mapInterface}
 | 
			
		||||
          </div>
 | 
			
		||||
          <div className="absolute top-0 right-0 w-14 h-[calc(100%+3.5rem)] pointer-events-auto">
 | 
			
		||||
            <RightBar onShowOnTheMap={handleShowOnTheMap} />
 | 
			
		||||
            <RightBar onShowOnTheMap={handleShowOnTheMap} onShowMapSettings={handleShowMapSettings} />
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      ) : (
 | 
			
		||||
        <div className="absolute top-0 left-14 w-[calc(100%-3.5rem)] h-[calc(100%-3.5rem)] pointer-events-none">
 | 
			
		||||
          <Topbar>
 | 
			
		||||
            <MapContextMenu onShowOnTheMap={handleShowOnTheMap} />
 | 
			
		||||
            <MapContextMenu onShowOnTheMap={handleShowOnTheMap} onShowMapSettings={handleShowMapSettings} />
 | 
			
		||||
          </Topbar>
 | 
			
		||||
          {mapInterface}
 | 
			
		||||
        </div>
 | 
			
		||||
      )}
 | 
			
		||||
      <OnTheMap show={showOnTheMap} onHide={() => setShowOnTheMap(false)} />
 | 
			
		||||
      <MapSettings show={showMapSettings} onHide={() => setShowMapSettings(false)} />
 | 
			
		||||
    </Layout>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,20 @@
 | 
			
		||||
import classes from './Connections.module.scss';
 | 
			
		||||
import { Sidebar } from 'primereact/sidebar';
 | 
			
		||||
import { useEffect, useMemo, useState } from 'react';
 | 
			
		||||
import { useEffect, useMemo, useState, useCallback } from 'react';
 | 
			
		||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
 | 
			
		||||
import { VirtualScroller, VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
 | 
			
		||||
import clsx from 'clsx';
 | 
			
		||||
import { ConnectionOutput, OutCommand, Passage, SolarSystemConnection } from '@/hooks/Mapper/types';
 | 
			
		||||
import {
 | 
			
		||||
  ConnectionOutput,
 | 
			
		||||
  ConnectionInfoOutput,
 | 
			
		||||
  OutCommand,
 | 
			
		||||
  Passage,
 | 
			
		||||
  SolarSystemConnection,
 | 
			
		||||
} from '@/hooks/Mapper/types';
 | 
			
		||||
import { PassageCard } from './PassageCard';
 | 
			
		||||
import { InfoDrawer, SystemView } from '@/hooks/Mapper/components/ui-kit';
 | 
			
		||||
import { kgToTons } from '@/hooks/Mapper/utils/kgToTons.ts';
 | 
			
		||||
import { TimeAgo } from '@/hooks/Mapper/components/ui-kit';
 | 
			
		||||
 | 
			
		||||
const sortByDate = (a: string, b: string) => new Date(a).getTime() - new Date(b).getTime();
 | 
			
		||||
 | 
			
		||||
@@ -69,25 +76,44 @@ export const Connections = ({ selectedConnection, onHide }: OnTheMapProps) => {
 | 
			
		||||
  }, [connections, selectedConnection]);
 | 
			
		||||
 | 
			
		||||
  const [passages, setPassages] = useState<Passage[]>([]);
 | 
			
		||||
  const [info, setInfo] = useState<ConnectionInfoOutput>(null);
 | 
			
		||||
 | 
			
		||||
  const loadInfo = useCallback(
 | 
			
		||||
    async (connection: SolarSystemConnection) => {
 | 
			
		||||
      const result = await outCommand<ConnectionInfoOutput>({
 | 
			
		||||
        type: OutCommand.getConnectionInfo,
 | 
			
		||||
        data: {
 | 
			
		||||
          from: connection.source,
 | 
			
		||||
          to: connection.target,
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      setInfo(result);
 | 
			
		||||
    },
 | 
			
		||||
    [outCommand],
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const loadPassages = useCallback(
 | 
			
		||||
    async (connection: SolarSystemConnection) => {
 | 
			
		||||
      const result = await outCommand<ConnectionOutput>({
 | 
			
		||||
        type: OutCommand.getPassages,
 | 
			
		||||
        data: {
 | 
			
		||||
          from: connection.source,
 | 
			
		||||
          to: connection.target,
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      setPassages(result.passages.sort((a, b) => sortByDate(b.inserted_at, a.inserted_at)));
 | 
			
		||||
    },
 | 
			
		||||
    [outCommand],
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!selectedConnection) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const loadInfo = async () => {
 | 
			
		||||
      const result = await outCommand<ConnectionOutput>({
 | 
			
		||||
        type: OutCommand.getPassages,
 | 
			
		||||
        data: {
 | 
			
		||||
          from: selectedConnection.source,
 | 
			
		||||
          to: selectedConnection.target,
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      setPassages(result.passages.sort((a, b) => sortByDate(b.inserted_at, a.inserted_at)));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    loadInfo();
 | 
			
		||||
    loadInfo(selectedConnection);
 | 
			
		||||
    loadPassages(selectedConnection);
 | 
			
		||||
  }, [selectedConnection]);
 | 
			
		||||
 | 
			
		||||
  const approximateMass = useMemo(() => {
 | 
			
		||||
@@ -132,6 +158,10 @@ export const Connections = ({ selectedConnection, onHide }: OnTheMapProps) => {
 | 
			
		||||
            {kgToTons(approximateMass)}
 | 
			
		||||
          </InfoDrawer>
 | 
			
		||||
 | 
			
		||||
          <InfoDrawer title="Mark EOL Time" rightSide>
 | 
			
		||||
            {info?.marl_eol_time ? <TimeAgo timestamp={info.marl_eol_time} /> : ' unknown '}
 | 
			
		||||
          </InfoDrawer>
 | 
			
		||||
 | 
			
		||||
          <div className="flex gap-2"></div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,10 +8,11 @@ import { MenuItem } from 'primereact/menuitem';
 | 
			
		||||
 | 
			
		||||
export interface MapContextMenuProps {
 | 
			
		||||
  onShowOnTheMap?: () => void;
 | 
			
		||||
  onShowMapSettings?: () => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const MapContextMenu = ({ onShowOnTheMap }: MapContextMenuProps) => {
 | 
			
		||||
  const { outCommand, interfaceSettings, setInterfaceSettings } = useMapRootState();
 | 
			
		||||
export const MapContextMenu = ({ onShowOnTheMap, onShowMapSettings }: MapContextMenuProps) => {
 | 
			
		||||
  const { outCommand, setInterfaceSettings } = useMapRootState();
 | 
			
		||||
 | 
			
		||||
  const menuRight = useRef<Menu>(null);
 | 
			
		||||
 | 
			
		||||
@@ -22,13 +23,6 @@ export const MapContextMenu = ({ onShowOnTheMap }: MapContextMenuProps) => {
 | 
			
		||||
    });
 | 
			
		||||
  }, [outCommand]);
 | 
			
		||||
 | 
			
		||||
  const toggleMinimap = useCallback(() => {
 | 
			
		||||
    setInterfaceSettings(x => ({
 | 
			
		||||
      ...x,
 | 
			
		||||
      isShowMinimap: !x.isShowMinimap,
 | 
			
		||||
    }));
 | 
			
		||||
  }, [setInterfaceSettings]);
 | 
			
		||||
 | 
			
		||||
  const items = useMemo(() => {
 | 
			
		||||
    return [
 | 
			
		||||
      {
 | 
			
		||||
@@ -43,9 +37,9 @@ export const MapContextMenu = ({ onShowOnTheMap }: MapContextMenuProps) => {
 | 
			
		||||
      },
 | 
			
		||||
      { separator: true },
 | 
			
		||||
      {
 | 
			
		||||
        label: interfaceSettings.isShowMinimap ? 'Hide minimap' : 'Show minimap',
 | 
			
		||||
        icon: `pi ${interfaceSettings.isShowMinimap ? 'pi-eye-slash' : 'pi-eye'}`,
 | 
			
		||||
        command: toggleMinimap,
 | 
			
		||||
        label: 'Settings',
 | 
			
		||||
        icon: `pi pi-cog`,
 | 
			
		||||
        command: onShowMapSettings,
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        label: 'Dock menu',
 | 
			
		||||
@@ -57,7 +51,7 @@ export const MapContextMenu = ({ onShowOnTheMap }: MapContextMenuProps) => {
 | 
			
		||||
          })),
 | 
			
		||||
      },
 | 
			
		||||
    ] as MenuItem[];
 | 
			
		||||
  }, [handleAddCharacter, interfaceSettings.isShowMinimap, onShowOnTheMap, setInterfaceSettings, toggleMinimap]);
 | 
			
		||||
  }, [handleAddCharacter, onShowMapSettings, onShowOnTheMap, setInterfaceSettings]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="ml-1">
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,127 @@
 | 
			
		||||
.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;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.CheckboxContainer {
 | 
			
		||||
  display: grid;
 | 
			
		||||
  grid-template-columns: auto 1fr auto;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
 | 
			
		||||
  & > span:nth-child(1) {
 | 
			
		||||
    color: var(--gray-200);
 | 
			
		||||
    font-size: 13px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  & > :nth-child(2){
 | 
			
		||||
    border-bottom: 2px dotted #3f3f3f;
 | 
			
		||||
    height: 2px;
 | 
			
		||||
    margin: 0 12px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Уменьшение размеров InputSwitch с использованием глобальных стилей */
 | 
			
		||||
.smallInputSwitch {
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
 | 
			
		||||
  :global {
 | 
			
		||||
    .p-inputswitch {
 | 
			
		||||
      height: 1rem;
 | 
			
		||||
      width: 2rem;
 | 
			
		||||
      &.p-inputswitch-checked {
 | 
			
		||||
        .p-inputswitch-slider::before {
 | 
			
		||||
          transform: translateX(1rem);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      &.p-highlight .p-inputswitch-slider:before {
 | 
			
		||||
        transform: translateX(1rem);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .p-inputswitch-slider::before {
 | 
			
		||||
        width: 0.8rem;
 | 
			
		||||
        height: 0.8rem;
 | 
			
		||||
        margin-top: -0.4rem;
 | 
			
		||||
        margin-left: -3px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,176 @@
 | 
			
		||||
import styles from './MapSettings.module.scss';
 | 
			
		||||
import { Dialog } from 'primereact/dialog';
 | 
			
		||||
import { useCallback, useMemo, useState } from 'react';
 | 
			
		||||
import { TabPanel, TabView } from 'primereact/tabview';
 | 
			
		||||
import { PrettySwitchbox } from './components';
 | 
			
		||||
import { InterfaceStoredSettings, InterfaceStoredSettingsProps, useMapRootState } from '@/hooks/Mapper/mapRootProvider';
 | 
			
		||||
import { OutCommand } from '@/hooks/Mapper/types';
 | 
			
		||||
 | 
			
		||||
export enum UserSettingsRemoteProps {
 | 
			
		||||
  link_signature_on_splash = 'link_signature_on_splash',
 | 
			
		||||
  select_on_spash = 'select_on_spash',
 | 
			
		||||
  delete_connection_with_sigs = 'delete_connection_with_sigs',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const DEFAULT_REMOTE_SETTINGS = {
 | 
			
		||||
  [UserSettingsRemoteProps.link_signature_on_splash]: false,
 | 
			
		||||
  [UserSettingsRemoteProps.select_on_spash]: false,
 | 
			
		||||
  [UserSettingsRemoteProps.delete_connection_with_sigs]: false,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const UserSettingsRemoteList = [
 | 
			
		||||
  UserSettingsRemoteProps.link_signature_on_splash,
 | 
			
		||||
  UserSettingsRemoteProps.select_on_spash,
 | 
			
		||||
  UserSettingsRemoteProps.delete_connection_with_sigs,
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export type UserSettingsRemote = {
 | 
			
		||||
  link_signature_on_splash: boolean;
 | 
			
		||||
  select_on_spash: boolean;
 | 
			
		||||
  delete_connection_with_sigs: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type UserSettings = UserSettingsRemote & InterfaceStoredSettings;
 | 
			
		||||
 | 
			
		||||
export interface MapSettingsProps {
 | 
			
		||||
  show: boolean;
 | 
			
		||||
  onHide: () => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CheckboxesList = {
 | 
			
		||||
  prop: keyof UserSettings;
 | 
			
		||||
  label: string;
 | 
			
		||||
}[];
 | 
			
		||||
 | 
			
		||||
const COMMON_CHECKBOXES_PROPS: CheckboxesList = [
 | 
			
		||||
  { prop: InterfaceStoredSettingsProps.isShowMinimap, label: 'Show Minimap' },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const SYSTEMS_CHECKBOXES_PROPS: CheckboxesList = [
 | 
			
		||||
  { prop: InterfaceStoredSettingsProps.isShowKSpace, label: 'Highlight Low/High-security systems' },
 | 
			
		||||
  { prop: UserSettingsRemoteProps.select_on_spash, label: 'Auto-select splashed' },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const SIGNATURES_CHECKBOXES_PROPS: CheckboxesList = [
 | 
			
		||||
  { prop: UserSettingsRemoteProps.link_signature_on_splash, label: 'Link signature on splash' },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const CONNECTIONS_CHECKBOXES_PROPS: CheckboxesList = [
 | 
			
		||||
  { prop: UserSettingsRemoteProps.delete_connection_with_sigs, label: 'Delete connections to linked signatures' },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const UI_CHECKBOXES_PROPS: CheckboxesList = [
 | 
			
		||||
  { prop: InterfaceStoredSettingsProps.isShowMenu, label: 'Enable compact map menu bar' },
 | 
			
		||||
  { prop: InterfaceStoredSettingsProps.isThickConnections, label: 'Thicker connections' },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export const MapSettings = ({ show, onHide }: MapSettingsProps) => {
 | 
			
		||||
  const [activeIndex, setActiveIndex] = useState(0);
 | 
			
		||||
  const { outCommand, interfaceSettings, setInterfaceSettings } = useMapRootState();
 | 
			
		||||
  const [userRemoteSettings, setUserRemoteSettings] = useState<UserSettingsRemote>({ ...DEFAULT_REMOTE_SETTINGS });
 | 
			
		||||
 | 
			
		||||
  const mergedSettings = useMemo(() => {
 | 
			
		||||
    return {
 | 
			
		||||
      ...interfaceSettings,
 | 
			
		||||
      ...userRemoteSettings,
 | 
			
		||||
    };
 | 
			
		||||
  }, [userRemoteSettings, interfaceSettings]);
 | 
			
		||||
 | 
			
		||||
  const handleShow = async () => {
 | 
			
		||||
    const { user_settings } = await outCommand({
 | 
			
		||||
      type: OutCommand.getUserSettings,
 | 
			
		||||
      data: null,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    setUserRemoteSettings({
 | 
			
		||||
      ...user_settings,
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleChangeChecked = useCallback(
 | 
			
		||||
    (prop: keyof UserSettings) => async (checked: boolean) => {
 | 
			
		||||
      // @ts-ignore
 | 
			
		||||
      if (UserSettingsRemoteList.includes(prop)) {
 | 
			
		||||
        const newRemoteSettings = {
 | 
			
		||||
          ...userRemoteSettings,
 | 
			
		||||
          [prop]: checked,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        await outCommand({
 | 
			
		||||
          type: OutCommand.updateUserSettings,
 | 
			
		||||
          data: newRemoteSettings,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        setUserRemoteSettings(newRemoteSettings);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      setInterfaceSettings({
 | 
			
		||||
        ...interfaceSettings,
 | 
			
		||||
        [prop]: checked,
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    [interfaceSettings, outCommand, setInterfaceSettings, userRemoteSettings],
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const renderCheckboxesList = (list: CheckboxesList) => {
 | 
			
		||||
    return list.map(x => {
 | 
			
		||||
      return (
 | 
			
		||||
        <PrettySwitchbox
 | 
			
		||||
          key={x.prop}
 | 
			
		||||
          label={x.label}
 | 
			
		||||
          checked={mergedSettings[x.prop]}
 | 
			
		||||
          setChecked={handleChangeChecked(x.prop)}
 | 
			
		||||
        />
 | 
			
		||||
      );
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Dialog
 | 
			
		||||
      header="Map settings"
 | 
			
		||||
      visible={show}
 | 
			
		||||
      draggable={false}
 | 
			
		||||
      style={{ width: '550px' }}
 | 
			
		||||
      onShow={handleShow}
 | 
			
		||||
      onHide={() => {
 | 
			
		||||
        if (!show) {
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        setActiveIndex(0);
 | 
			
		||||
        onHide();
 | 
			
		||||
      }}
 | 
			
		||||
    >
 | 
			
		||||
      <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="Common" headerClassName={styles.verticalTabHeader}>
 | 
			
		||||
                <div className="w-full h-full flex flex-col gap-1">{renderCheckboxesList(COMMON_CHECKBOXES_PROPS)}</div>
 | 
			
		||||
              </TabPanel>
 | 
			
		||||
              <TabPanel header="Systems" headerClassName={styles.verticalTabHeader}>
 | 
			
		||||
                <div className="w-full h-full flex flex-col gap-1">
 | 
			
		||||
                  {renderCheckboxesList(SYSTEMS_CHECKBOXES_PROPS)}
 | 
			
		||||
                </div>
 | 
			
		||||
              </TabPanel>
 | 
			
		||||
              <TabPanel header="Connections" headerClassName={styles.verticalTabHeader}>
 | 
			
		||||
                {renderCheckboxesList(CONNECTIONS_CHECKBOXES_PROPS)}
 | 
			
		||||
              </TabPanel>
 | 
			
		||||
              <TabPanel header="Signatures" headerClassName={styles.verticalTabHeader}>
 | 
			
		||||
                {renderCheckboxesList(SIGNATURES_CHECKBOXES_PROPS)}
 | 
			
		||||
              </TabPanel>
 | 
			
		||||
              <TabPanel header="User Interface" headerClassName={styles.verticalTabHeader}>
 | 
			
		||||
                {renderCheckboxesList(UI_CHECKBOXES_PROPS)}
 | 
			
		||||
              </TabPanel>
 | 
			
		||||
            </TabView>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </Dialog>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
@@ -0,0 +1,48 @@
 | 
			
		||||
.CheckboxContainer {
 | 
			
		||||
  display: grid;
 | 
			
		||||
  grid-template-columns: auto 1fr auto;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
 | 
			
		||||
  & > span:nth-child(1) {
 | 
			
		||||
    color: var(--gray-200);
 | 
			
		||||
    font-size: 13px;
 | 
			
		||||
    user-select: none;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  & > :nth-child(2){
 | 
			
		||||
    border-bottom: 2px dotted #3f3f3f;
 | 
			
		||||
    height: 1px;
 | 
			
		||||
    margin: 0 12px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Уменьшение размеров InputSwitch с использованием глобальных стилей */
 | 
			
		||||
.smallInputSwitch {
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
 | 
			
		||||
  :global {
 | 
			
		||||
    .p-inputswitch {
 | 
			
		||||
      height: 1rem;
 | 
			
		||||
      width: 2rem;
 | 
			
		||||
      &.p-inputswitch-checked {
 | 
			
		||||
        .p-inputswitch-slider::before {
 | 
			
		||||
          transform: translateX(1rem);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      &.p-highlight .p-inputswitch-slider:before {
 | 
			
		||||
        transform: translateX(1rem);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .p-inputswitch-slider::before {
 | 
			
		||||
        width: 0.8rem;
 | 
			
		||||
        height: 0.8rem;
 | 
			
		||||
        margin-top: -0.4rem;
 | 
			
		||||
        margin-left: -3px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,20 @@
 | 
			
		||||
import styles from './MapSettings.module.scss';
 | 
			
		||||
import { WdCheckbox } from '@/hooks/Mapper/components/ui-kit';
 | 
			
		||||
 | 
			
		||||
interface PrettySwitchboxProps {
 | 
			
		||||
  checked: boolean;
 | 
			
		||||
  setChecked: (checked: boolean) => void;
 | 
			
		||||
  label: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const PrettySwitchbox = ({ checked, setChecked, label }: PrettySwitchboxProps) => {
 | 
			
		||||
  return (
 | 
			
		||||
    <label className={styles.CheckboxContainer}>
 | 
			
		||||
      <span>{label}</span>
 | 
			
		||||
      <div />
 | 
			
		||||
      <div className={styles.smallInputSwitch}>
 | 
			
		||||
        <WdCheckbox size="m" label={''} value={checked} onChange={e => setChecked(e.checked ?? false)} />
 | 
			
		||||
      </div>
 | 
			
		||||
    </label>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
export * from './PrettySwitchbox';
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
export * from './PrettySwitchbox';
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
export * from './MapSettings';
 | 
			
		||||
@@ -8,11 +8,14 @@ import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit';
 | 
			
		||||
 | 
			
		||||
interface RightBarProps {
 | 
			
		||||
  onShowOnTheMap?: () => void;
 | 
			
		||||
  onShowMapSettings?: () => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const RightBar = ({ onShowOnTheMap }: RightBarProps) => {
 | 
			
		||||
export const RightBar = ({ onShowOnTheMap, onShowMapSettings }: RightBarProps) => {
 | 
			
		||||
  const { outCommand, interfaceSettings, setInterfaceSettings } = useMapRootState();
 | 
			
		||||
 | 
			
		||||
  const isShowMinimap = interfaceSettings.isShowMinimap === undefined ? true : interfaceSettings.isShowMinimap;
 | 
			
		||||
 | 
			
		||||
  const handleAddCharacter = useCallback(() => {
 | 
			
		||||
    outCommand({
 | 
			
		||||
      type: OutCommand.addCharacter,
 | 
			
		||||
@@ -27,6 +30,13 @@ export const RightBar = ({ onShowOnTheMap }: RightBarProps) => {
 | 
			
		||||
    }));
 | 
			
		||||
  }, [setInterfaceSettings]);
 | 
			
		||||
 | 
			
		||||
  const toggleKSpace = useCallback(() => {
 | 
			
		||||
    setInterfaceSettings(x => ({
 | 
			
		||||
      ...x,
 | 
			
		||||
      isShowKSpace: !x.isShowKSpace,
 | 
			
		||||
    }));
 | 
			
		||||
  }, [setInterfaceSettings]);
 | 
			
		||||
 | 
			
		||||
  const toggleMenu = useCallback(() => {
 | 
			
		||||
    setInterfaceSettings(x => ({
 | 
			
		||||
      ...x,
 | 
			
		||||
@@ -50,7 +60,7 @@ export const RightBar = ({ onShowOnTheMap }: RightBarProps) => {
 | 
			
		||||
            type="button"
 | 
			
		||||
            onClick={handleAddCharacter}
 | 
			
		||||
          >
 | 
			
		||||
            <i className="pi pi-user-plus text-lg"></i>
 | 
			
		||||
            <i className="pi pi-user-plus"></i>
 | 
			
		||||
          </button>
 | 
			
		||||
        </WdTooltipWrapper>
 | 
			
		||||
 | 
			
		||||
@@ -60,26 +70,44 @@ export const RightBar = ({ onShowOnTheMap }: RightBarProps) => {
 | 
			
		||||
            type="button"
 | 
			
		||||
            onClick={onShowOnTheMap}
 | 
			
		||||
          >
 | 
			
		||||
            <i className="pi pi-hashtag text-lg"></i>
 | 
			
		||||
            <i className="pi pi-hashtag"></i>
 | 
			
		||||
          </button>
 | 
			
		||||
        </WdTooltipWrapper>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div className="flex flex-col items-center mb-2 gap-1">
 | 
			
		||||
        <WdTooltipWrapper content="User settings" position={TooltipPosition.left}>
 | 
			
		||||
          <button
 | 
			
		||||
            className="btn bg-transparent text-gray-400 hover:text-white border-transparent hover:bg-transparent py-2 h-auto min-h-auto"
 | 
			
		||||
            type="button"
 | 
			
		||||
            onClick={onShowMapSettings}
 | 
			
		||||
          >
 | 
			
		||||
            <i className="pi pi-cog"></i>
 | 
			
		||||
          </button>
 | 
			
		||||
        </WdTooltipWrapper>
 | 
			
		||||
 | 
			
		||||
        <WdTooltipWrapper
 | 
			
		||||
          content={interfaceSettings.isShowMinimap ? 'Hide minimap' : 'Show minimap'}
 | 
			
		||||
          content={
 | 
			
		||||
            interfaceSettings.isShowKSpace ? 'Hide highlighting Imperial Space' : 'Show highlighting Imperial Space'
 | 
			
		||||
          }
 | 
			
		||||
          position={TooltipPosition.left}
 | 
			
		||||
        >
 | 
			
		||||
          <button
 | 
			
		||||
            className="btn bg-transparent text-gray-400 hover:text-white border-transparent hover:bg-transparent py-2 h-auto min-h-auto"
 | 
			
		||||
            type="button"
 | 
			
		||||
            onClick={toggleKSpace}
 | 
			
		||||
          >
 | 
			
		||||
            <i className={interfaceSettings.isShowKSpace ? 'hero-cloud-solid' : 'hero-cloud'}></i>
 | 
			
		||||
          </button>
 | 
			
		||||
        </WdTooltipWrapper>
 | 
			
		||||
 | 
			
		||||
        <WdTooltipWrapper content={isShowMinimap ? 'Hide minimap' : 'Show minimap'} position={TooltipPosition.left}>
 | 
			
		||||
          <button
 | 
			
		||||
            className="btn bg-transparent text-gray-400 hover:text-white border-transparent hover:bg-transparent py-2 h-auto min-h-auto"
 | 
			
		||||
            type="button"
 | 
			
		||||
            onClick={toggleMinimap}
 | 
			
		||||
          >
 | 
			
		||||
            {interfaceSettings.isShowMinimap ? (
 | 
			
		||||
              <i className="pi pi-eye text-lg"></i>
 | 
			
		||||
            ) : (
 | 
			
		||||
              <i className="pi pi-eye-slash text-lg"></i>
 | 
			
		||||
            )}
 | 
			
		||||
            <i className={isShowMinimap ? 'pi pi-eye' : 'pi pi-eye-slash'}></i>
 | 
			
		||||
          </button>
 | 
			
		||||
        </WdTooltipWrapper>
 | 
			
		||||
 | 
			
		||||
@@ -89,7 +117,7 @@ export const RightBar = ({ onShowOnTheMap }: RightBarProps) => {
 | 
			
		||||
            type="button"
 | 
			
		||||
            onClick={toggleMenu}
 | 
			
		||||
          >
 | 
			
		||||
            <i className="pi pi-window-minimize text-lg"></i>
 | 
			
		||||
            <i className="pi pi-window-minimize"></i>
 | 
			
		||||
          </button>
 | 
			
		||||
        </WdTooltipWrapper>
 | 
			
		||||
      </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,10 @@
 | 
			
		||||
import { createGenericContext } from '@/hooks/Mapper/utils/abstractContextProvider.tsx';
 | 
			
		||||
 | 
			
		||||
export interface SystemsSettingsProvider {
 | 
			
		||||
  systemId: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const { Provider, useContextValue } = createGenericContext<SystemsSettingsProvider>();
 | 
			
		||||
 | 
			
		||||
export const SystemsSettingsProvider = Provider;
 | 
			
		||||
export const useSystemsSettingsProvider = useContextValue;
 | 
			
		||||
@@ -0,0 +1,81 @@
 | 
			
		||||
.verticalTabsContainer {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  min-height: 300px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.verticalTabsContainer {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  min-height: 300px;
 | 
			
		||||
 | 
			
		||||
  :global {
 | 
			
		||||
    .p-tabview {
 | 
			
		||||
      width: 100%;
 | 
			
		||||
      display: flex;
 | 
			
		||||
      align-items: flex-start;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .p-tabview-panels {
 | 
			
		||||
      padding: 6px 1rem !important;
 | 
			
		||||
      flex-grow: 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .p-tabview-nav-container {
 | 
			
		||||
      border-right: none;
 | 
			
		||||
      height: 100%;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .p-tabview-nav {
 | 
			
		||||
      flex-direction: column;
 | 
			
		||||
      width: 150px;
 | 
			
		||||
      min-height: 100%;
 | 
			
		||||
      border: none;
 | 
			
		||||
 | 
			
		||||
      li {
 | 
			
		||||
        width: 100%;
 | 
			
		||||
        border-right: 4px solid var(--surface-hover);
 | 
			
		||||
        background-color: var(--surface-card);
 | 
			
		||||
 | 
			
		||||
        transition: background-color 200ms, border-right-color 200ms;
 | 
			
		||||
 | 
			
		||||
        &:hover {
 | 
			
		||||
          background-color: var(--surface-hover);
 | 
			
		||||
          border-right: 4px solid var(--surface-100);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .p-tabview-nav-link {
 | 
			
		||||
          transition: color 200ms;
 | 
			
		||||
 | 
			
		||||
          justify-content: flex-end;
 | 
			
		||||
          padding: 10px;
 | 
			
		||||
          background-color: initial;
 | 
			
		||||
          border: none;
 | 
			
		||||
          color: var(--gray-400);
 | 
			
		||||
 | 
			
		||||
          border-radius: initial;
 | 
			
		||||
          font-weight: 400;
 | 
			
		||||
          margin: 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        &.p-tabview-selected {
 | 
			
		||||
          background-color: var(--surface-50);
 | 
			
		||||
          border-right: 4px solid var(--primary-color);
 | 
			
		||||
 | 
			
		||||
          .p-tabview-nav-link {
 | 
			
		||||
            font-weight: 600;
 | 
			
		||||
            color: var(--primary-color);
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          &:hover {
 | 
			
		||||
            border-right: 4px solid var(--primary-color);
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .p-tabview-panel {
 | 
			
		||||
      flex-grow: 1;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,171 @@
 | 
			
		||||
import { Dialog } from 'primereact/dialog';
 | 
			
		||||
import { useCallback, useEffect } from 'react';
 | 
			
		||||
// import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
 | 
			
		||||
import { OutCommand, SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
 | 
			
		||||
import { Controller, FormProvider, useForm } from 'react-hook-form';
 | 
			
		||||
import {
 | 
			
		||||
  SignatureGroupContent,
 | 
			
		||||
  SignatureGroupSelect,
 | 
			
		||||
} from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components';
 | 
			
		||||
import { InputText } from 'primereact/inputtext';
 | 
			
		||||
import { SystemsSettingsProvider } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/Provider.tsx';
 | 
			
		||||
import { Button } from 'primereact/button';
 | 
			
		||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
 | 
			
		||||
 | 
			
		||||
type SystemSignaturePrepared = Omit<SystemSignature, 'linked_system'> & { linked_system: string };
 | 
			
		||||
 | 
			
		||||
export interface MapSettingsProps {
 | 
			
		||||
  systemId: string;
 | 
			
		||||
  show: boolean;
 | 
			
		||||
  onHide: () => void;
 | 
			
		||||
  signatureData: SystemSignature | null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const SignatureSettings = ({ systemId, show, onHide, signatureData }: MapSettingsProps) => {
 | 
			
		||||
  const { outCommand } = useMapRootState();
 | 
			
		||||
 | 
			
		||||
  const handleShow = async () => {};
 | 
			
		||||
  const form = useForm<Partial<SystemSignaturePrepared>>({});
 | 
			
		||||
 | 
			
		||||
  const handleSave = useCallback(async () => {
 | 
			
		||||
    if (!signatureData) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const { group, ...values } = form.getValues();
 | 
			
		||||
    let out = { ...signatureData };
 | 
			
		||||
 | 
			
		||||
    switch (group) {
 | 
			
		||||
      case SignatureGroup.Wormhole:
 | 
			
		||||
        if (values.linked_system) {
 | 
			
		||||
          await outCommand({
 | 
			
		||||
            type: OutCommand.linkSignatureToSystem,
 | 
			
		||||
            data: {
 | 
			
		||||
              signature_eve_id: signatureData.eve_id,
 | 
			
		||||
              solar_system_source: systemId,
 | 
			
		||||
              solar_system_target: values.linked_system,
 | 
			
		||||
            },
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (values.type != null) {
 | 
			
		||||
          out = { ...out, type: values.type };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (signatureData.group !== SignatureGroup.Wormhole) {
 | 
			
		||||
          out = { ...out, name: '' };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        break;
 | 
			
		||||
      case SignatureGroup.CosmicSignature:
 | 
			
		||||
        out = { ...out, type: '', name: '' };
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        if (values.name != null) {
 | 
			
		||||
          out = { ...out, name: values.name ?? '' };
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (values.description != null) {
 | 
			
		||||
      out = { ...out, description: values.description };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Note: when type of signature changed from WH to other type - we should drop name
 | 
			
		||||
    if (
 | 
			
		||||
      group !== SignatureGroup.Wormhole && // new
 | 
			
		||||
      signatureData.group === SignatureGroup.Wormhole && // prev
 | 
			
		||||
      signatureData.linked_system
 | 
			
		||||
    ) {
 | 
			
		||||
      await outCommand({
 | 
			
		||||
        type: OutCommand.unlinkSignature,
 | 
			
		||||
        data: { signature_eve_id: signatureData.eve_id, solar_system_source: systemId },
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      out = { ...out, type: '' };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (group === SignatureGroup.Wormhole && signatureData.linked_system != null && values.linked_system === null) {
 | 
			
		||||
      await outCommand({
 | 
			
		||||
        type: OutCommand.unlinkSignature,
 | 
			
		||||
        data: { signature_eve_id: signatureData.eve_id, solar_system_source: systemId },
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Note: despite groups have optional type - this will always set
 | 
			
		||||
    out = { ...out, group: group! };
 | 
			
		||||
 | 
			
		||||
    await outCommand({
 | 
			
		||||
      type: OutCommand.updateSignatures,
 | 
			
		||||
      data: {
 | 
			
		||||
        system_id: systemId,
 | 
			
		||||
        added: [],
 | 
			
		||||
        updated: [out],
 | 
			
		||||
        removed: [],
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    form.reset();
 | 
			
		||||
    onHide();
 | 
			
		||||
  }, [form, onHide, outCommand, signatureData, systemId]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!signatureData) {
 | 
			
		||||
      form.reset();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const { linked_system, ...rest } = signatureData;
 | 
			
		||||
 | 
			
		||||
    form.reset({
 | 
			
		||||
      linked_system: linked_system?.solar_system_id.toString() ?? undefined,
 | 
			
		||||
      ...rest,
 | 
			
		||||
    });
 | 
			
		||||
  }, [form, signatureData]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Dialog
 | 
			
		||||
      header={`Signature Edit [${signatureData?.eve_id}]`}
 | 
			
		||||
      visible={show}
 | 
			
		||||
      draggable={false}
 | 
			
		||||
      style={{ width: '390px' }}
 | 
			
		||||
      onShow={handleShow}
 | 
			
		||||
      onHide={() => {
 | 
			
		||||
        if (!show) {
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        onHide();
 | 
			
		||||
      }}
 | 
			
		||||
    >
 | 
			
		||||
      <SystemsSettingsProvider initialValue={{ systemId }}>
 | 
			
		||||
        <FormProvider {...form}>
 | 
			
		||||
          <div className="flex flex-col gap-2 justify-between">
 | 
			
		||||
            <div className="w-full flex flex-col gap-1 p-1 min-h-[150px]">
 | 
			
		||||
              <label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
 | 
			
		||||
                <span>Group:</span>
 | 
			
		||||
                <SignatureGroupSelect name="group" />
 | 
			
		||||
              </label>
 | 
			
		||||
 | 
			
		||||
              <SignatureGroupContent />
 | 
			
		||||
 | 
			
		||||
              <label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
 | 
			
		||||
                <span>Description:</span>
 | 
			
		||||
                <Controller
 | 
			
		||||
                  name="description"
 | 
			
		||||
                  control={form.control}
 | 
			
		||||
                  render={({ field }) => (
 | 
			
		||||
                    <InputText placeholder="Type description" value={field.value} onChange={field.onChange} />
 | 
			
		||||
                  )}
 | 
			
		||||
                />
 | 
			
		||||
              </label>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div className="flex gap-2 justify-end">
 | 
			
		||||
              <Button onClick={handleSave} outlined size="small" label="Save"></Button>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </FormProvider>
 | 
			
		||||
      </SystemsSettingsProvider>
 | 
			
		||||
    </Dialog>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
@@ -0,0 +1,45 @@
 | 
			
		||||
import { Controller, useFormContext } from 'react-hook-form';
 | 
			
		||||
import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
 | 
			
		||||
import { useSystemsSettingsProvider } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/Provider.tsx';
 | 
			
		||||
import { SignatureGroupContentWormholes } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureGroupContentWormholes.tsx';
 | 
			
		||||
import { InputText } from 'primereact/inputtext';
 | 
			
		||||
 | 
			
		||||
export interface SignatureGroupContentProps {}
 | 
			
		||||
 | 
			
		||||
export const SignatureGroupContent = ({}: SignatureGroupContentProps) => {
 | 
			
		||||
  const { watch, control } = useFormContext<SystemSignature>();
 | 
			
		||||
  const group = watch('group');
 | 
			
		||||
 | 
			
		||||
  const {
 | 
			
		||||
    value: { systemId },
 | 
			
		||||
  } = useSystemsSettingsProvider();
 | 
			
		||||
 | 
			
		||||
  if (!systemId) {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (group === SignatureGroup.Wormhole) {
 | 
			
		||||
    return (
 | 
			
		||||
      <>
 | 
			
		||||
        <SignatureGroupContentWormholes />
 | 
			
		||||
      </>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (group === SignatureGroup.CosmicSignature) {
 | 
			
		||||
    return <div></div>;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div>
 | 
			
		||||
      <label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
 | 
			
		||||
        <span>Name:</span>
 | 
			
		||||
        <Controller
 | 
			
		||||
          name="name"
 | 
			
		||||
          control={control}
 | 
			
		||||
          render={({ field }) => <InputText placeholder="Name" value={field.value} onChange={field.onChange} />}
 | 
			
		||||
        />
 | 
			
		||||
      </label>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
export * from './SignatureGroupContent';
 | 
			
		||||
@@ -0,0 +1,18 @@
 | 
			
		||||
import { SignatureWormholeTypeSelect } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureWormholeTypeSelect';
 | 
			
		||||
import { SignatureLeadsToSelect } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureLeadsToSelect';
 | 
			
		||||
 | 
			
		||||
export const SignatureGroupContentWormholes = () => {
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
 | 
			
		||||
        <span>Type:</span>
 | 
			
		||||
        <SignatureWormholeTypeSelect name="type" />
 | 
			
		||||
      </label>
 | 
			
		||||
 | 
			
		||||
      <label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
 | 
			
		||||
        <span>Leads To:</span>
 | 
			
		||||
        <SignatureLeadsToSelect name="linked_system" />
 | 
			
		||||
      </label>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
@@ -0,0 +1,59 @@
 | 
			
		||||
import { Dropdown } from 'primereact/dropdown';
 | 
			
		||||
import clsx from 'clsx';
 | 
			
		||||
import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
 | 
			
		||||
import { renderIcon } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders';
 | 
			
		||||
import { Controller, useFormContext } from 'react-hook-form';
 | 
			
		||||
 | 
			
		||||
const signatureGroupOptions = Object.keys(SignatureGroup).map(x => ({
 | 
			
		||||
  value: SignatureGroup[x as keyof typeof SignatureGroup],
 | 
			
		||||
  label: SignatureGroup[x as keyof typeof SignatureGroup],
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
// @ts-ignore
 | 
			
		||||
const renderSignatureTemplate = option => {
 | 
			
		||||
  if (!option) {
 | 
			
		||||
    return 'No group selected';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex gap-2 items-center">
 | 
			
		||||
      <span className="w-[20px] mt-[1px] flex justify-center items-center">
 | 
			
		||||
        {renderIcon(
 | 
			
		||||
          { group: option.label } as SystemSignature,
 | 
			
		||||
          option.label === SignatureGroup.CosmicSignature ? { w: 10, h: 10 } : { w: 16, h: 16 },
 | 
			
		||||
        )}
 | 
			
		||||
      </span>
 | 
			
		||||
      <span>{option.label}</span>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export interface SignatureGroupSelectProps {
 | 
			
		||||
  name: string;
 | 
			
		||||
  defaultValue?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const SignatureGroupSelect = ({ name, defaultValue = '' }: SignatureGroupSelectProps) => {
 | 
			
		||||
  const { control } = useFormContext();
 | 
			
		||||
  return (
 | 
			
		||||
    <Controller
 | 
			
		||||
      name={name}
 | 
			
		||||
      control={control}
 | 
			
		||||
      defaultValue={defaultValue}
 | 
			
		||||
      render={({ field }) => (
 | 
			
		||||
        <Dropdown
 | 
			
		||||
          value={field.value}
 | 
			
		||||
          onChange={field.onChange}
 | 
			
		||||
          options={signatureGroupOptions}
 | 
			
		||||
          optionLabel="label"
 | 
			
		||||
          optionValue="value"
 | 
			
		||||
          placeholder="Select group"
 | 
			
		||||
          className={clsx('w-full')}
 | 
			
		||||
          scrollHeight="240px"
 | 
			
		||||
          itemTemplate={renderSignatureTemplate}
 | 
			
		||||
          valueTemplate={renderSignatureTemplate}
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
export * from './SignatureGroupSelect';
 | 
			
		||||
@@ -0,0 +1,3 @@
 | 
			
		||||
.SystemView {
 | 
			
		||||
  font-size: 14px !important;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,108 @@
 | 
			
		||||
import { Dropdown } from 'primereact/dropdown';
 | 
			
		||||
import clsx from 'clsx';
 | 
			
		||||
import { Controller, useFormContext } from 'react-hook-form';
 | 
			
		||||
import { useSystemsSettingsProvider } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/Provider.tsx';
 | 
			
		||||
import { useSystemInfo } from '@/hooks/Mapper/components/hooks';
 | 
			
		||||
import { useMemo } from 'react';
 | 
			
		||||
import { SystemView } from '@/hooks/Mapper/components/ui-kit';
 | 
			
		||||
import classes from './SignatureLeadsToSelect.module.scss';
 | 
			
		||||
import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts';
 | 
			
		||||
import { SystemSignature } from '@/hooks/Mapper/types';
 | 
			
		||||
import { WORMHOLES_ADDITIONAL_INFO_BY_CLASS_ID } from '@/hooks/Mapper/components/map/constants.ts';
 | 
			
		||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
 | 
			
		||||
 | 
			
		||||
// @ts-ignore
 | 
			
		||||
const renderLinkedSystemItem = (option: { value: string }) => {
 | 
			
		||||
  if (option.value == null) {
 | 
			
		||||
    return <div className="flex gap-2 items-center">No linked system</div>;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex gap-2 items-center">
 | 
			
		||||
      <SystemView systemId={option.value} className={classes.SystemView} />
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// @ts-ignore
 | 
			
		||||
const renderLinkedSystemValue = (option: { value: string }) => {
 | 
			
		||||
  if (!option) {
 | 
			
		||||
    return 'Select Leads To system';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (option.value == null) {
 | 
			
		||||
    return 'Select Leads To system';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex gap-2 items-center">
 | 
			
		||||
      <SystemView systemId={option.value} className={classes.SystemView} />
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const renderLeadsToEmpty = () => <div className="flex items-center text-[14px]">No wormhole to select</div>;
 | 
			
		||||
 | 
			
		||||
export interface SignatureLeadsToSelectProps {
 | 
			
		||||
  name: string;
 | 
			
		||||
  defaultValue?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const SignatureLeadsToSelect = ({ name, defaultValue = '' }: SignatureLeadsToSelectProps) => {
 | 
			
		||||
  const { control, watch } = useFormContext<SystemSignature>();
 | 
			
		||||
  const group = watch('type');
 | 
			
		||||
 | 
			
		||||
  const {
 | 
			
		||||
    value: { systemId },
 | 
			
		||||
  } = useSystemsSettingsProvider();
 | 
			
		||||
 | 
			
		||||
  const { leadsTo } = useSystemInfo({ systemId });
 | 
			
		||||
  const { systems: systemStatics } = useLoadSystemStatic({ systems: leadsTo });
 | 
			
		||||
  const {
 | 
			
		||||
    data: { wormholes },
 | 
			
		||||
  } = useMapRootState();
 | 
			
		||||
 | 
			
		||||
  const leadsToOptions = useMemo(() => {
 | 
			
		||||
    return [
 | 
			
		||||
      { value: null },
 | 
			
		||||
      ...leadsTo
 | 
			
		||||
        .filter(systemId => {
 | 
			
		||||
          const systemStatic = systemStatics.get(parseInt(systemId));
 | 
			
		||||
          const whInfo = wormholes.find(x => x.name === group);
 | 
			
		||||
 | 
			
		||||
          if (!systemStatic || !whInfo || group === 'K162') {
 | 
			
		||||
            return true;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          const { id: whType } = WORMHOLES_ADDITIONAL_INFO_BY_CLASS_ID[systemStatic.system_class];
 | 
			
		||||
          return whInfo.dest === whType;
 | 
			
		||||
        })
 | 
			
		||||
        .map(x => ({ value: x })),
 | 
			
		||||
    ];
 | 
			
		||||
  }, [group, leadsTo, systemStatics, wormholes]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Controller
 | 
			
		||||
      // @ts-ignore
 | 
			
		||||
      name={name}
 | 
			
		||||
      control={control}
 | 
			
		||||
      defaultValue={defaultValue}
 | 
			
		||||
      render={({ field }) => {
 | 
			
		||||
        return (
 | 
			
		||||
          <Dropdown
 | 
			
		||||
            value={field.value}
 | 
			
		||||
            onChange={field.onChange}
 | 
			
		||||
            options={leadsToOptions}
 | 
			
		||||
            optionValue="value"
 | 
			
		||||
            placeholder="Select Leads To wormhole"
 | 
			
		||||
            className={clsx('w-full')}
 | 
			
		||||
            scrollHeight="240px"
 | 
			
		||||
            itemTemplate={renderLinkedSystemItem}
 | 
			
		||||
            valueTemplate={renderLinkedSystemValue}
 | 
			
		||||
            emptyMessage={renderLeadsToEmpty}
 | 
			
		||||
          />
 | 
			
		||||
        );
 | 
			
		||||
      }}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
export * from './SignatureLeadsToSelect.tsx';
 | 
			
		||||
@@ -0,0 +1,134 @@
 | 
			
		||||
import { Dropdown } from 'primereact/dropdown';
 | 
			
		||||
import clsx from 'clsx';
 | 
			
		||||
import { Respawn, SolarSystemStaticInfoRaw, WormholeDataRaw } from '@/hooks/Mapper/types';
 | 
			
		||||
import { Controller, useFormContext } from 'react-hook-form';
 | 
			
		||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
 | 
			
		||||
import { useSystemsSettingsProvider } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/Provider.tsx';
 | 
			
		||||
import { useSystemInfo } from '@/hooks/Mapper/components/hooks';
 | 
			
		||||
import {
 | 
			
		||||
  SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS,
 | 
			
		||||
  WORMHOLES_ADDITIONAL_INFO_BY_CLASS_ID,
 | 
			
		||||
} from '@/hooks/Mapper/components/map/constants.ts';
 | 
			
		||||
import { useMemo } from 'react';
 | 
			
		||||
import { WHClassView } from '@/hooks/Mapper/components/ui-kit';
 | 
			
		||||
 | 
			
		||||
const getPossibleWormholes = (systemStatic: SolarSystemStaticInfoRaw, wormholes: WormholeDataRaw[]) => {
 | 
			
		||||
  const { id: whType } = WORMHOLES_ADDITIONAL_INFO_BY_CLASS_ID[systemStatic.system_class];
 | 
			
		||||
 | 
			
		||||
  // @ts-ignore
 | 
			
		||||
  const spawnClassGroup = SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS[whType];
 | 
			
		||||
  const possibleWHTypes = wormholes.filter(x => x.src.includes(spawnClassGroup));
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    statics: possibleWHTypes
 | 
			
		||||
      .filter(x => x.respawn.some(y => y === Respawn.static))
 | 
			
		||||
      .filter(x => systemStatic.statics.includes(x.name)),
 | 
			
		||||
    k162: wormholes.find(x => x.name === 'K162')!,
 | 
			
		||||
    wanderings: possibleWHTypes.filter(x => x.respawn.some(y => y === Respawn.wandering)),
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// @ts-ignore
 | 
			
		||||
const renderWHTypeGroupTemplate = option => {
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex gap-2 items-center">
 | 
			
		||||
      <span>{option.label}</span>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// @ts-ignore
 | 
			
		||||
const renderWHTypeTemplateValue = (option: { label: string; data: WormholeDataRaw }) => {
 | 
			
		||||
  if (!option) {
 | 
			
		||||
    return 'Select wormhole type';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex gap-2 items-center">
 | 
			
		||||
      <WHClassView whClassName={option.data.name} noOffset useShortTitle />
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// @ts-ignore
 | 
			
		||||
const renderWHTypeTemplate = (option: { label: string; data: WormholeDataRaw }) => {
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex gap-2 items-center ml-[1rem]">
 | 
			
		||||
      <WHClassView whClassName={option.data.name} noOffset useShortTitle />
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export interface SignatureGroupSelectProps {
 | 
			
		||||
  name: string;
 | 
			
		||||
  defaultValue?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const SignatureWormholeTypeSelect = ({ name, defaultValue = '' }: SignatureGroupSelectProps) => {
 | 
			
		||||
  const { control } = useFormContext();
 | 
			
		||||
 | 
			
		||||
  const {
 | 
			
		||||
    data: { wormholes },
 | 
			
		||||
  } = useMapRootState();
 | 
			
		||||
 | 
			
		||||
  const {
 | 
			
		||||
    value: { systemId },
 | 
			
		||||
  } = useSystemsSettingsProvider();
 | 
			
		||||
 | 
			
		||||
  const system = useSystemInfo({ systemId });
 | 
			
		||||
 | 
			
		||||
  const possibleWormholesOptions = useMemo(() => {
 | 
			
		||||
    const possibleWormholes = getPossibleWormholes(system.staticInfo, wormholes);
 | 
			
		||||
 | 
			
		||||
    return [
 | 
			
		||||
      {
 | 
			
		||||
        label: 'Statics',
 | 
			
		||||
        items: [
 | 
			
		||||
          ...possibleWormholes.statics.map(x => ({
 | 
			
		||||
            label: x.name,
 | 
			
		||||
            value: x.name,
 | 
			
		||||
            data: x,
 | 
			
		||||
          })),
 | 
			
		||||
          {
 | 
			
		||||
            value: possibleWormholes.k162.name,
 | 
			
		||||
            label: possibleWormholes.k162.name,
 | 
			
		||||
            data: possibleWormholes.k162,
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        label: 'Wanderings',
 | 
			
		||||
        items: possibleWormholes.wanderings.map(x => ({
 | 
			
		||||
          label: x.name,
 | 
			
		||||
          value: x.name,
 | 
			
		||||
          data: x,
 | 
			
		||||
        })),
 | 
			
		||||
      },
 | 
			
		||||
    ];
 | 
			
		||||
  }, [system, wormholes]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Controller
 | 
			
		||||
      name={name}
 | 
			
		||||
      control={control}
 | 
			
		||||
      defaultValue={defaultValue}
 | 
			
		||||
      render={({ field }) => (
 | 
			
		||||
        <Dropdown
 | 
			
		||||
          value={field.value}
 | 
			
		||||
          onChange={field.onChange}
 | 
			
		||||
          options={possibleWormholesOptions}
 | 
			
		||||
          optionLabel="label"
 | 
			
		||||
          optionValue="value"
 | 
			
		||||
          placeholder="Select wormhole type"
 | 
			
		||||
          optionGroupLabel="label"
 | 
			
		||||
          optionGroupChildren="items"
 | 
			
		||||
          className={clsx('w-full')}
 | 
			
		||||
          scrollHeight="240px"
 | 
			
		||||
          optionGroupTemplate={renderWHTypeGroupTemplate}
 | 
			
		||||
          itemTemplate={renderWHTypeTemplate}
 | 
			
		||||
          valueTemplate={renderWHTypeTemplateValue}
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
export * from './SignatureWormholeTypeSelect';
 | 
			
		||||
@@ -0,0 +1,2 @@
 | 
			
		||||
export * from './SignatureGroupSelect';
 | 
			
		||||
export * from './SignatureGroupContent';
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
export * from './SignatureSettings.tsx';
 | 
			
		||||
@@ -1,37 +1,64 @@
 | 
			
		||||
import { Map } from '@/hooks/Mapper/components/map/Map.tsx';
 | 
			
		||||
import { ForwardedRef, useCallback, useRef, useState } from 'react';
 | 
			
		||||
import { MapHandlers, OutCommand, OutCommandHandler, SolarSystemConnection } from '@/hooks/Mapper/types';
 | 
			
		||||
import { useCallback, useRef, useState } from 'react';
 | 
			
		||||
import { OutCommand, OutCommandHandler, SolarSystemConnection } from '@/hooks/Mapper/types';
 | 
			
		||||
import { MapRootData, useMapRootState } from '@/hooks/Mapper/mapRootProvider';
 | 
			
		||||
import { OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts';
 | 
			
		||||
import isEqual from 'lodash.isequal';
 | 
			
		||||
import { ContextMenuSystem, useContextMenuSystemHandlers } from '@/hooks/Mapper/components/contexts';
 | 
			
		||||
import { SystemCustomLabelDialog, SystemSettingsDialog } from '@/hooks/Mapper/components/mapInterface/components';
 | 
			
		||||
import {
 | 
			
		||||
  SystemCustomLabelDialog,
 | 
			
		||||
  SystemLinkSignatureDialog,
 | 
			
		||||
  SystemSettingsDialog,
 | 
			
		||||
} from '@/hooks/Mapper/components/mapInterface/components';
 | 
			
		||||
import classes from './MapWrapper.module.scss';
 | 
			
		||||
import { Connections } from '@/hooks/Mapper/components/mapRootContent/components/Connections';
 | 
			
		||||
import { ContextMenuSystemMultiple, useContextMenuSystemMultipleHandlers } from '../contexts/ContextMenuSystemMultiple';
 | 
			
		||||
import { getSystemById } from '@/hooks/Mapper/helpers';
 | 
			
		||||
import { Node } from 'reactflow';
 | 
			
		||||
 | 
			
		||||
import { STORED_INTERFACE_DEFAULT_VALUES } from '@/hooks/Mapper/mapRootProvider/MapRootProvider';
 | 
			
		||||
import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts';
 | 
			
		||||
import { useMapEventListener } from '@/hooks/Mapper/events';
 | 
			
		||||
 | 
			
		||||
interface MapWrapperProps {
 | 
			
		||||
  refn: ForwardedRef<MapHandlers>;
 | 
			
		||||
}
 | 
			
		||||
import { STORED_INTERFACE_DEFAULT_VALUES } from '@/hooks/Mapper/mapRootProvider/MapRootProvider';
 | 
			
		||||
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
 | 
			
		||||
import { useCommonMapEventProcessor } from '@/hooks/Mapper/components/mapWrapper/hooks/useCommonMapEventProcessor.ts';
 | 
			
		||||
 | 
			
		||||
// TODO: INFO - this component needs for abstract work with Map instance
 | 
			
		||||
export const MapWrapper = ({ refn }: MapWrapperProps) => {
 | 
			
		||||
export const MapWrapper = () => {
 | 
			
		||||
  const {
 | 
			
		||||
    update,
 | 
			
		||||
    outCommand,
 | 
			
		||||
    data: { selectedConnections, selectedSystems, hubs, systems },
 | 
			
		||||
    interfaceSettings: { isShowMenu, isShowMinimap = STORED_INTERFACE_DEFAULT_VALUES.isShowMinimap },
 | 
			
		||||
    interfaceSettings: {
 | 
			
		||||
      isShowMenu,
 | 
			
		||||
      isShowMinimap = STORED_INTERFACE_DEFAULT_VALUES.isShowMinimap,
 | 
			
		||||
      isShowKSpace,
 | 
			
		||||
      isThickConnections,
 | 
			
		||||
    },
 | 
			
		||||
  } = useMapRootState();
 | 
			
		||||
  const { deleteSystems } = useDeleteSystems();
 | 
			
		||||
  const { mapRef, runCommand } = useCommonMapEventProcessor();
 | 
			
		||||
 | 
			
		||||
  const { open, ...systemContextProps } = useContextMenuSystemHandlers({ systems, hubs, outCommand });
 | 
			
		||||
  const { handleSystemMultipleContext, ...systemMultipleCtxProps } = useContextMenuSystemMultipleHandlers();
 | 
			
		||||
 | 
			
		||||
  const ref = useRef({ selectedConnections, selectedSystems, systemContextProps, systems });
 | 
			
		||||
  ref.current = { selectedConnections, selectedSystems, systemContextProps, systems };
 | 
			
		||||
  const [openSettings, setOpenSettings] = useState<string | null>(null);
 | 
			
		||||
  const [openLinkSignatures, setOpenLinkSignatures] = useState<any | null>(null);
 | 
			
		||||
  const [openCustomLabel, setOpenCustomLabel] = useState<string | null>(null);
 | 
			
		||||
  const [selectedConnection, setSelectedConnection] = useState<SolarSystemConnection | null>(null);
 | 
			
		||||
 | 
			
		||||
  const ref = useRef({ selectedConnections, selectedSystems, systemContextProps, systems, deleteSystems });
 | 
			
		||||
  ref.current = { selectedConnections, selectedSystems, systemContextProps, systems, deleteSystems };
 | 
			
		||||
 | 
			
		||||
  useMapEventListener(event => {
 | 
			
		||||
    switch (event.name) {
 | 
			
		||||
      case Commands.linkSignatureToSystem:
 | 
			
		||||
        setOpenLinkSignatures(event.data);
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    runCommand(event);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const onSelectionChange: OnMapSelectionChange = useCallback(
 | 
			
		||||
    ({ systems, connections }) => {
 | 
			
		||||
@@ -52,14 +79,15 @@ export const MapWrapper = ({ refn }: MapWrapperProps) => {
 | 
			
		||||
    [update],
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const [openSettings, setOpenSettings] = useState<string | null>(null);
 | 
			
		||||
  const [openCustomLabel, setOpenCustomLabel] = useState<string | null>(null);
 | 
			
		||||
  const handleCommand: OutCommandHandler = useCallback(
 | 
			
		||||
    event => {
 | 
			
		||||
      switch (event.type) {
 | 
			
		||||
        case OutCommand.openSettings:
 | 
			
		||||
          setOpenSettings(event.data.system_id);
 | 
			
		||||
          break;
 | 
			
		||||
        case OutCommand.linkSignatureToSystem:
 | 
			
		||||
          setOpenLinkSignatures(event.data);
 | 
			
		||||
          break;
 | 
			
		||||
        default:
 | 
			
		||||
          return outCommand(event);
 | 
			
		||||
      }
 | 
			
		||||
@@ -84,14 +112,19 @@ export const MapWrapper = ({ refn }: MapWrapperProps) => {
 | 
			
		||||
    [open],
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const [selectedConnection, setSelectedConnection] = useState<SolarSystemConnection | null>(null);
 | 
			
		||||
 | 
			
		||||
  const handleConnectionDbClick = useCallback((e: SolarSystemConnection) => setSelectedConnection(e), []);
 | 
			
		||||
 | 
			
		||||
  const handleManualDelete = useCallback((toDelete: string[]) => {
 | 
			
		||||
    const restDel = toDelete.filter(x => ref.current.systems.some(y => y.id === x));
 | 
			
		||||
    if (restDel.length > 0) {
 | 
			
		||||
      ref.current.deleteSystems(restDel);
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <Map
 | 
			
		||||
        ref={refn}
 | 
			
		||||
        ref={mapRef}
 | 
			
		||||
        onCommand={handleCommand}
 | 
			
		||||
        onSelectionChange={onSelectionChange}
 | 
			
		||||
        onConnectionInfoClick={handleConnectionDbClick}
 | 
			
		||||
@@ -99,22 +132,21 @@ export const MapWrapper = ({ refn }: MapWrapperProps) => {
 | 
			
		||||
        onSelectionContextMenu={handleSystemMultipleContext}
 | 
			
		||||
        minimapClasses={!isShowMenu ? classes.MiniMap : undefined}
 | 
			
		||||
        isShowMinimap={isShowMinimap}
 | 
			
		||||
        showKSpaceBG={isShowKSpace}
 | 
			
		||||
        onManualDelete={handleManualDelete}
 | 
			
		||||
        isThickConnections={isThickConnections}
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
      {openSettings != null && (
 | 
			
		||||
        <SystemSettingsDialog
 | 
			
		||||
          systemId={openSettings}
 | 
			
		||||
          visible={openSettings != null}
 | 
			
		||||
          setVisible={() => setOpenSettings(null)}
 | 
			
		||||
        />
 | 
			
		||||
        <SystemSettingsDialog systemId={openSettings} visible setVisible={() => setOpenSettings(null)} />
 | 
			
		||||
      )}
 | 
			
		||||
 | 
			
		||||
      {openCustomLabel != null && (
 | 
			
		||||
        <SystemCustomLabelDialog
 | 
			
		||||
          systemId={openCustomLabel}
 | 
			
		||||
          visible={openCustomLabel != null}
 | 
			
		||||
          setVisible={() => setOpenCustomLabel(null)}
 | 
			
		||||
        />
 | 
			
		||||
        <SystemCustomLabelDialog systemId={openCustomLabel} visible setVisible={() => setOpenCustomLabel(null)} />
 | 
			
		||||
      )}
 | 
			
		||||
 | 
			
		||||
      {openLinkSignatures != null && (
 | 
			
		||||
        <SystemLinkSignatureDialog data={openLinkSignatures} setVisible={() => setOpenLinkSignatures(null)} />
 | 
			
		||||
      )}
 | 
			
		||||
 | 
			
		||||
      <Connections selectedConnection={selectedConnection} onHide={() => setSelectedConnection(null)} />
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,38 @@
 | 
			
		||||
import { MutableRefObject, useCallback, useEffect, useRef } from 'react';
 | 
			
		||||
import { Command, Commands, MapHandlers } from '@/hooks/Mapper/types';
 | 
			
		||||
import { MapEvent } from '@/hooks/Mapper/events';
 | 
			
		||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
 | 
			
		||||
 | 
			
		||||
export const useCommonMapEventProcessor = () => {
 | 
			
		||||
  const mapRef = useRef<MapHandlers>() as MutableRefObject<MapHandlers>;
 | 
			
		||||
  const {
 | 
			
		||||
    data: { systems },
 | 
			
		||||
  } = useMapRootState();
 | 
			
		||||
 | 
			
		||||
  const refQueue = useRef<MapEvent<Command>[]>([]);
 | 
			
		||||
 | 
			
		||||
  // const ref = useRef({})
 | 
			
		||||
 | 
			
		||||
  const runCommand = useCallback(({ name, data }: MapEvent<Command>) => {
 | 
			
		||||
    switch (name) {
 | 
			
		||||
      case Commands.addSystems:
 | 
			
		||||
      case Commands.removeSystems:
 | 
			
		||||
        // case Commands.addConnections:
 | 
			
		||||
        refQueue.current.push({ name, data });
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // @ts-ignore hz why here type error
 | 
			
		||||
    mapRef.current?.command(name, data);
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    refQueue.current.forEach(x => mapRef.current?.command(x.name, x.data));
 | 
			
		||||
    refQueue.current = [];
 | 
			
		||||
  }, [systems]);
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    mapRef,
 | 
			
		||||
    runCommand,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
@@ -4,7 +4,7 @@ import classes from './CharacterCard.module.scss';
 | 
			
		||||
import { SystemView } from '@/hooks/Mapper/components/ui-kit/SystemView';
 | 
			
		||||
import { CharacterTypeRaw, WithIsOwnCharacter } from '@/hooks/Mapper/types';
 | 
			
		||||
import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts';
 | 
			
		||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
 | 
			
		||||
import { emitMapEvent } from '@/hooks/Mapper/events';
 | 
			
		||||
 | 
			
		||||
type CharacterCardProps = {
 | 
			
		||||
  compact?: boolean;
 | 
			
		||||
@@ -34,11 +34,12 @@ export const CharacterCard = ({
 | 
			
		||||
  useSystemsCache,
 | 
			
		||||
  ...char
 | 
			
		||||
}: CharacterCardProps) => {
 | 
			
		||||
  const { mapRef } = useMapRootState();
 | 
			
		||||
 | 
			
		||||
  const handleSelect = useCallback(() => {
 | 
			
		||||
    mapRef.current?.command(Commands.selectSystem, char?.location?.solar_system_id?.toString());
 | 
			
		||||
  }, [mapRef, char]);
 | 
			
		||||
    emitMapEvent({
 | 
			
		||||
      name: Commands.centerSystem,
 | 
			
		||||
      data: char?.location?.solar_system_id?.toString(),
 | 
			
		||||
    });
 | 
			
		||||
  }, [char]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={clsx(classes.CharacterCard, 'w-full text-xs', 'flex flex-col box-border')} onClick={handleSelect}>
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,11 @@
 | 
			
		||||
.WHClassViewContent {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  gap: 2px;
 | 
			
		||||
 | 
			
		||||
  &.NoOffset {
 | 
			
		||||
    gap: 4px;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.WHClassName {
 | 
			
		||||
@@ -13,3 +18,12 @@
 | 
			
		||||
  font-weight: bold;
 | 
			
		||||
  top: -2px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.NoOffset {
 | 
			
		||||
  *.WHClassName {
 | 
			
		||||
    position: relative;
 | 
			
		||||
    font-size: 12px;
 | 
			
		||||
    font-weight: initial !important;
 | 
			
		||||
    top: initial !important;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -16,26 +16,42 @@ const prepareMass = (mass: number) => {
 | 
			
		||||
 | 
			
		||||
export interface WHClassViewProps {
 | 
			
		||||
  whClassName: string;
 | 
			
		||||
  noOffset?: boolean;
 | 
			
		||||
  useShortTitle?: boolean;
 | 
			
		||||
  hideWhClass?: boolean;
 | 
			
		||||
  highlightName?: boolean;
 | 
			
		||||
  className?: string;
 | 
			
		||||
  classNameWh?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const WHClassView = ({ whClassName }: WHClassViewProps) => {
 | 
			
		||||
export const WHClassView = ({
 | 
			
		||||
  whClassName,
 | 
			
		||||
  noOffset,
 | 
			
		||||
  useShortTitle,
 | 
			
		||||
  hideWhClass,
 | 
			
		||||
  highlightName,
 | 
			
		||||
  className,
 | 
			
		||||
  classNameWh,
 | 
			
		||||
}: WHClassViewProps) => {
 | 
			
		||||
  const {
 | 
			
		||||
    data: { wormholesData },
 | 
			
		||||
  } = useMapRootState();
 | 
			
		||||
 | 
			
		||||
  const whData = useMemo(() => wormholesData[whClassName], [whClassName, wormholesData]);
 | 
			
		||||
  const whClass = useMemo(() => WORMHOLES_ADDITIONAL_INFO[whData.dest], [whData.dest]);
 | 
			
		||||
  const whClassStyle = WORMHOLE_CLASS_STYLES[whClass.wormholeClassID];
 | 
			
		||||
  const whClassStyle = WORMHOLE_CLASS_STYLES[whClass?.wormholeClassID] ?? '';
 | 
			
		||||
 | 
			
		||||
  const uid = useMemo(() => new Date().getTime().toString(), []);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={classes.WHClassViewRoot}>
 | 
			
		||||
    <div className={clsx(classes.WHClassViewRoot, className)}>
 | 
			
		||||
      <Tooltip
 | 
			
		||||
        target={`.wh-name${whClassName}`}
 | 
			
		||||
        target={`.wh-name${whClassName}${uid}`}
 | 
			
		||||
        position="right"
 | 
			
		||||
        mouseTrack
 | 
			
		||||
        mouseTrackLeft={20}
 | 
			
		||||
        mouseTrackTop={30}
 | 
			
		||||
        className="border border-green-300 rounded border-opacity-10 bg-stone-900 bg-opacity-70 "
 | 
			
		||||
        className="border border-green-300 rounded border-opacity-10 bg-stone-900 bg-opacity-90 "
 | 
			
		||||
      >
 | 
			
		||||
        <div className="flex gap-3">
 | 
			
		||||
          <div className="flex flex-col gap-1">
 | 
			
		||||
@@ -49,9 +65,20 @@ export const WHClassView = ({ whClassName }: WHClassViewProps) => {
 | 
			
		||||
        </div>
 | 
			
		||||
      </Tooltip>
 | 
			
		||||
 | 
			
		||||
      <div className={clsx(classes.WHClassViewContent, 'wh-name select-none cursor-help', `wh-name${whClassName}`)}>
 | 
			
		||||
        <span>{whClassName}</span>
 | 
			
		||||
        <span className={clsx(classes.WHClassName, whClassStyle)}>{whClass.shortName}</span>
 | 
			
		||||
      <div
 | 
			
		||||
        className={clsx(
 | 
			
		||||
          classes.WHClassViewContent,
 | 
			
		||||
          { [classes.NoOffset]: noOffset },
 | 
			
		||||
          'wh-name select-none cursor-help',
 | 
			
		||||
          `wh-name${whClassName}${uid}`,
 | 
			
		||||
        )}
 | 
			
		||||
      >
 | 
			
		||||
        <span className={clsx({ [whClassStyle]: highlightName })}>{whClassName}</span>
 | 
			
		||||
        {!hideWhClass && whClass && (
 | 
			
		||||
          <span className={clsx(classes.WHClassName, whClassStyle, classNameWh)}>
 | 
			
		||||
            {useShortTitle ? whClass.shortTitle : whClass.shortName}
 | 
			
		||||
          </span>
 | 
			
		||||
        )}
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
 
 | 
			
		||||
@@ -5,3 +5,62 @@ export enum SESSION_KEY {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const GRADIENT_MENU_ACTIVE_CLASSES = 'bg-gradient-to-br from-transparent/10 to-fuchsia-300/10';
 | 
			
		||||
 | 
			
		||||
export enum Regions {
 | 
			
		||||
  Derelik = 10000001,
 | 
			
		||||
  TheForge = 10000002,
 | 
			
		||||
  Lonetrek = 10000016,
 | 
			
		||||
  SinqLaison = 10000032,
 | 
			
		||||
  Aridia = 10000054,
 | 
			
		||||
  BlackRise = 10000069,
 | 
			
		||||
  TheBleakLands = 10000038,
 | 
			
		||||
  TheCitadel = 10000033,
 | 
			
		||||
  Devoid = 10000036,
 | 
			
		||||
  Domain = 10000043,
 | 
			
		||||
  Essence = 10000064,
 | 
			
		||||
  Everyshore = 10000037,
 | 
			
		||||
  Genesis = 10000067,
 | 
			
		||||
  Heimatar = 10000030,
 | 
			
		||||
  Kador = 10000052,
 | 
			
		||||
  Khanid = 10000049,
 | 
			
		||||
  KorAzor = 10000065,
 | 
			
		||||
  Metropolis = 10000042,
 | 
			
		||||
  MoldenHeath = 10000028,
 | 
			
		||||
  Placid = 10000048,
 | 
			
		||||
  Solitude = 10000044,
 | 
			
		||||
  TashMurkon = 10000020,
 | 
			
		||||
  VergeVendor = 10000068,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export enum Spaces {
 | 
			
		||||
  'Caldari' = 'Caldari',
 | 
			
		||||
  'Gallente' = 'Gallente',
 | 
			
		||||
  'Matar' = 'Matar',
 | 
			
		||||
  'Amarr' = 'Amarr',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const REGIONS_MAP: Record<number, Spaces> = {
 | 
			
		||||
  [Regions.Derelik]: Spaces.Amarr,
 | 
			
		||||
  [Regions.TheForge]: Spaces.Caldari,
 | 
			
		||||
  [Regions.Lonetrek]: Spaces.Caldari,
 | 
			
		||||
  [Regions.SinqLaison]: Spaces.Gallente,
 | 
			
		||||
  [Regions.Aridia]: Spaces.Amarr,
 | 
			
		||||
  [Regions.BlackRise]: Spaces.Caldari,
 | 
			
		||||
  [Regions.TheBleakLands]: Spaces.Amarr,
 | 
			
		||||
  [Regions.TheCitadel]: Spaces.Caldari,
 | 
			
		||||
  [Regions.Devoid]: Spaces.Amarr,
 | 
			
		||||
  [Regions.Domain]: Spaces.Amarr,
 | 
			
		||||
  [Regions.Essence]: Spaces.Gallente,
 | 
			
		||||
  [Regions.Everyshore]: Spaces.Gallente,
 | 
			
		||||
  [Regions.Genesis]: Spaces.Amarr,
 | 
			
		||||
  [Regions.Heimatar]: Spaces.Matar,
 | 
			
		||||
  [Regions.Kador]: Spaces.Amarr,
 | 
			
		||||
  [Regions.Khanid]: Spaces.Amarr,
 | 
			
		||||
  [Regions.KorAzor]: Spaces.Amarr,
 | 
			
		||||
  [Regions.Metropolis]: Spaces.Matar,
 | 
			
		||||
  [Regions.MoldenHeath]: Spaces.Matar,
 | 
			
		||||
  [Regions.Placid]: Spaces.Gallente,
 | 
			
		||||
  [Regions.Solitude]: Spaces.Gallente,
 | 
			
		||||
  [Regions.TashMurkon]: Spaces.Amarr,
 | 
			
		||||
  [Regions.VergeVendor]: Spaces.Gallente,
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user