Compare commits

..

119 Commits

Author SHA1 Message Date
achichenkov
b549189644 fix(Map): fixed U210, K346 for C4 shattered systems 2024-12-16 17:13:33 +03:00
achichenkov
35279d17b4 fix(Map): fixed U210, K346 for shattered systems. Fixed mass of mediums chains. Fixed size of some capital chains from 3M to 3.3M. Based on https://whtype.info/ data. 2024-12-16 16:48:53 +03:00
achichenkov
bb403aa0c5 fix(Map): removed unnecessary log 2024-12-16 12:45:51 +03:00
achichenkov
04327c288b fix(Map): Uncomment what should not be commented 2024-12-16 12:43:26 +03:00
achichenkov
94d60e40d0 feat(Map): Fixed incorrect wrapping labels of checkboxes in System Signatures, Local and Routes. Also changed dotlan links for k-spacem now it leads to region map before, for wh all stay as it was. Added ability to chane to softer background and remove dots on background of map. Also some small design issues. #2 2024-12-16 12:42:24 +03:00
achichenkov
8505fcb6b7 feat(Map): Fixed incorrect wrapping labels of checkboxes in System Signatures, Local and Routes. Also changed dotlan links for k-spacem now it leads to region map before, for wh all stay as it was. Added ability to chane to softer background and remove dots on background of map. Also some small design issues. 2024-12-16 12:42:03 +03:00
CI
9aec57166d chore: release version v1.29.5
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-12-14 22:32:16 +00:00
Dmitry Popov
a3739f2950 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-12-14 23:31:47 +01:00
Dmitry Popov
3d3b152758 fix(Core): Fix character trackers cleanup 2024-12-14 23:31:42 +01:00
CI
0e03730543 chore: release version v1.29.4
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-12-10 11:01:09 +00:00
Dmitry Popov
97e07a6511 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-12-10 12:00:38 +01:00
Dmitry Popov
a77a51ba15 fix(Core): Small fixes 2024-12-10 12:00:34 +01:00
CI
42e706e1c2 chore: release version v1.29.3
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-12-07 18:02:05 +00:00
Dmitry Popov
025dd06053 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-12-07 19:01:36 +01:00
Dmitry Popov
bcb421d879 fix(Core): Increased eve DB data download timeout 2024-12-07 19:01:32 +01:00
CI
66056ab54b chore: release version v1.29.2
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-12-07 08:32:58 +00:00
Dmitry Popov
bb92f76ceb Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-12-07 09:32:28 +01:00
Dmitry Popov
84076b340b fix(Core): Fix unpkg CDN issues, fix Abyssals sites adding as systems on map 2024-12-07 09:32:24 +01:00
CI
48caae5c0e chore: release version v1.29.1
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-12-05 10:54:41 +00:00
Dmitry Popov
77dd23795a Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-12-05 11:54:05 +01:00
Dmitry Popov
2771d6304e chore: release version v1.28.1 2024-12-05 11:54:01 +01:00
CI
9946edffa4 chore: release version v1.29.0
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-12-05 08:52:42 +00:00
Dmitry Popov
50bf2fd9d3 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-12-05 09:52:13 +01:00
Dmitry Popov
bdcde168aa feat(Signatures): Show 'Unsplashed' signatures on the map (optionally) 2024-12-05 09:52:07 +01:00
CI
5807142e20 chore: release version v1.28.1
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-12-04 20:14:44 +00:00
Dmitry Popov
ec2d9565b9 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-12-04 21:14:18 +01:00
Dmitry Popov
a18a71c73d chore: release version v1.27.1 2024-12-04 21:14:14 +01:00
CI
93a6bd1156 chore: release version v1.28.0
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-12-04 17:02:43 +00:00
Dmitry Popov
581a410aef Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-12-04 18:02:08 +01:00
Dmitry Popov
ab02fe988c feat(Map): Added an option to show 'Offline characters' to map admins & managers only
- updated UI layout for map settings modal
2024-12-04 18:01:50 +01:00
CI
b8d20fb21b chore: release version v1.27.1
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-12-04 08:14:30 +00:00
Dmitry Popov
12fa1a0be8 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-12-04 09:13:57 +01:00
Dmitry Popov
85a84f7507 fix(Map): Fix 'On the map' visibility 2024-12-04 09:13:54 +01:00
CI
2385313013 chore: release version v1.27.0
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-12-03 21:04:59 +00:00
Dmitry Popov
c7ce727571 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-12-03 22:04:26 +01:00
Dmitry Popov
8b165ff478 feat(Map): Hide 'On the map' list for 'Viewer' role 2024-12-03 22:04:20 +01:00
CI
6d7d0cc72d chore: release version v1.26.1 2024-12-03 20:05:05 +00:00
Dmitry Popov
f7eba5d4fd Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-12-03 21:04:32 +01:00
Dmitry Popov
73ef6dae73 fix(Signatures): Fix error on splash wh 2024-12-03 21:04:26 +01:00
CI
7fa6df1e5e chore: release version v1.26.0
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-12-03 16:54:46 +00:00
Dmitry Popov
e1a2ffb151 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-12-03 17:54:18 +01:00
Dmitry Popov
6d7727a32d feat(Signatures): Keep 'Lazy delete' enabled setting 2024-12-03 17:54:13 +01:00
CI
6d7a94bd5a chore: release version v1.25.2
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-12-01 07:26:29 +00:00
Dmitry Popov
ecc3fb17e1 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-12-01 08:25:59 +01:00
Dmitry Popov
209e2bf0a5 fix(Signatures): Fix lazy delete on system switch 2024-12-01 08:25:10 +01:00
CI
b1947e57a4 chore: release version v1.25.1
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-11-28 23:20:10 +00:00
Dmitry Popov
74507501a5 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-11-29 00:19:43 +01:00
Dmitry Popov
c73481fd58 fix(Signatures): Fix colors & add 'Backspace' hotkey to delete signatures 2024-11-29 00:19:26 +01:00
CI
7795ad0b0c chore: release version v1.25.0
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-11-28 10:09:57 +00:00
Dmitry Popov
aff768f413 feat(Signatures): Automatically remove signature if linked system removed 2024-11-28 11:09:24 +01:00
CI
310b60f5b6 chore: release version v1.24.2
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-11-27 23:42:04 +00:00
Dmitry Popov
100f0be86a fix(Signatures): Fix paste signatures 2024-11-28 00:41:01 +01:00
CI
87e115e40d chore: release version v1.24.1
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-11-27 15:37:38 +00:00
Dmitry Popov
ef5f36e4c4 Signatures k162 types (#77)
* feat(Signatures): Added ability to specify K162 type (actually next types supported Hi/Low/Null/C1-C6/Thera/Pochven)
2024-11-27 19:37:10 +04:00
CI
099650420d chore: release version v1.24.0
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-11-27 13:28:03 +00:00
Dmitry Popov
8ccf7fffa5 feat(Signatures): Added "Lazy delete" option & got rid of update popup 2024-11-27 14:27:27 +01:00
CI
b97a055bf7 chore: release version v1.23.0
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-11-26 21:05:58 +00:00
Dmitry Popov
663fee6699 feat(Map): Lock systems available to manager/admin roles only (#75)
* feat(Map): Lock systems available to manager/admin roles only

* feat(Map): Fix add system & add acl member select behaviour
2024-11-27 01:05:26 +04:00
CI
33d5f3938b chore: release version v1.22.0 2024-11-26 19:03:01 +00:00
Aleksei Chichenkov
ef6b45d7a1 feat(Map): Rework design of checkboxes in Signatures settings dialog. Rework design of checkboxes in Routes settings dialog. Now signature will deleteing by Delete hotkey was Backspace. Fixed size of group column in signatures list. Instead Updated column will be Added, updated may be turn on in settings. (#76)
Co-authored-by: achichenkov <aleksei.chichenkov@telleqt.ai>
2024-11-26 23:02:30 +04:00
CI
c1ecd3690e chore: release version v1.21.0
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-11-24 15:55:38 +00:00
Aleksei Chichenkov
3250fe1ec6 Merge pull request #74 from wanderer-industries/dessign-issues-2
feat(Map): add new gate design, change EOL placement
2024-11-24 18:55:12 +03:00
achichenkov
48e8cd93b9 feat(Map): add new gate design, change EOL placement 2024-11-24 18:30:40 +03:00
CI
afacbb16b6 chore: release version v1.20.1
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-11-22 12:42:02 +00:00
Dmitry Popov
dfad127f32 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-11-22 13:41:35 +01:00
Dmitry Popov
300c1b5a18 chore: release version v1.19.3 2024-11-22 13:41:30 +01:00
CI
bb38e1710b chore: release version v1.20.0
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-11-22 12:27:53 +00:00
Dmitry Popov
0857a82de5 feat(Core): Add connection type for Gates, add new Update logic
fixes #73
2024-11-22 13:27:17 +01:00
CI
da5afcc91c chore: release version v1.19.3
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-11-20 14:09:27 +00:00
Dmitry Popov
0002979fda fix(Core): Fix adding systems on splash (#71)
* fix(Core): Fix adding systems on splash
2024-11-20 18:08:59 +04:00
CI
080af16d41 chore: release version v1.19.2
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-11-19 21:27:05 +00:00
Dmitry Popov
d03a0b7083 chore: release version v1.19.1 2024-11-19 22:26:22 +01:00
CI
5ba21f5386 chore: release version v1.19.1
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-11-19 16:07:12 +00:00
Dmitry Popov
10eeae5295 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-11-19 17:06:39 +01:00
Dmitry Popov
a5bead15d0 chore: release version v1.18.1 2024-11-19 17:06:35 +01:00
CI
0de674adde chore: release version v1.19.0 2024-11-19 14:22:53 +00:00
Dmitry Popov
1db65965d0 feat(Signatures): Add user setting to show Inserted time in a separate column 2024-11-19 15:22:16 +01:00
CI
bbed17f631 chore: release version v1.18.1
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-11-17 11:19:49 +00:00
Dmitry Popov
0af4a3a731 chore: release version v1.18.0 2024-11-17 12:19:22 +01:00
CI
49d503705a chore: release version v1.18.0
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-11-16 15:05:38 +00:00
Aleksei Chichenkov
c55dd7b8d9 Merge pull request #64 from wanderer-industries/design-issues
feat(Map): a lot of design issues
2024-11-16 18:05:08 +03:00
CI
7ddcab3537 chore: release version v1.17.0
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-11-15 22:35:12 +00:00
Dmitry Popov
040b46c345 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-11-15 23:34:38 +01:00
Dmitry Popov
cd11ab6775 feat(Signatures): Add user setting to show Description in a separate column 2024-11-15 23:34:33 +01:00
achichenkov
a5d776f3b1 feat(Map): a lot of design issues 2024-11-15 19:42:46 +03:00
CI
e02caf341d chore: release version v1.16.1
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-11-15 16:13:05 +00:00
Dmitry Popov
3c04caa67c Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-11-15 17:12:37 +01:00
Dmitry Popov
e76b564cbf fix(Signatures): Fix signature stored filters 2024-11-15 17:12:33 +01:00
CI
5b2de88c3d chore: release version v1.16.0
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-11-15 07:59:04 +00:00
Dmitry Popov
82080b232f feat(Signatures): Add additional filters support to signature list, show description icon 2024-11-15 08:58:25 +01:00
CI
666bc7af43 chore: release version v1.15.5
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-11-14 12:57:21 +00:00
Dmitry Popov
dc077d5a5b chore: release version v1.15.4 2024-11-14 13:56:52 +01:00
CI
29c840c64a chore: release version v1.15.4
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-11-14 08:28:05 +00:00
Dmitry Popov
65e0f89f33 fix(Core): Untracked characters still tracked on map (#63)
fixes #60
2024-11-14 12:27:37 +04:00
CI
d3b9b36332 chore: release version v1.15.3
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-11-13 07:47:06 +00:00
Dmitry Popov
90bbf29ea1 Db unix socket (#62)
* feat(Core): Add support for Unix sockets for DB connection

* chore: release version v1.15.2

* chore: release version v1.15.2
2024-11-13 11:46:39 +04:00
CI
57f73684e8 chore: release version v1.15.2
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-11-07 21:45:12 +00:00
Dmitry Popov
7833cdebb2 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-11-07 22:44:22 +01:00
Dmitry Popov
67a5ae2985 chore: release version v1.15.0 2024-11-07 22:44:19 +01:00
CI
f58c52d26b chore: release version v1.15.1 2024-11-07 21:42:31 +00:00
Dmitry Popov
41e7739461 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-11-07 22:41:52 +01:00
Dmitry Popov
332152b677 fix(Dev): Update .devcontainer instructions 2024-11-07 22:41:43 +01:00
CI
85b49fe1f0 chore: release version v1.15.0 2024-11-07 21:40:09 +00:00
Dmitry Popov
e7924532be feat(Connections): Add connection mark EOL time (#56) 2024-11-08 01:39:44 +04:00
CI
475d950ad6 chore: release version v1.14.1
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-11-06 14:13:14 +00:00
Dmitry Popov
e6cfb29c6f fix(Core): Fix character tracking permissions 2024-11-06 15:12:44 +01:00
CI
dee6e86db1 chore: release version v1.14.0
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-11-05 07:23:19 +00:00
Dmitry Popov
72f088331f Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-11-05 08:22:50 +01:00
Dmitry Popov
bbf536d10e feat(ACL): Add an ability to assign member role without DnD 2024-11-05 08:22:46 +01:00
CI
149ac98297 chore: release version v1.13.12
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-11-04 07:24:03 +00:00
Dmitry Popov
b90a2910c9 fix(Map): Fix system revert issues 2024-11-04 08:23:26 +01:00
CI
c4da8a3a8d chore: release version v1.13.11 2024-11-02 21:31:48 +00:00
Dmitry Popov
3ca75583d2 fix(Map): Fix system revert issues 2024-11-02 22:31:20 +01:00
CI
5f4607ae6f chore: release version v1.13.10 2024-11-01 14:54:49 +00:00
Dmitry Popov
d880c6873f Merge remote-tracking branch 'origin/main'
# Conflicts:
#	lib/wanderer_app/map/map_server_impl.ex
2024-11-01 15:53:05 +01:00
Dmitry Popov
937649b2ed fix(Map): Fix system revert issues 2024-11-01 15:51:15 +01:00
CI
78e912c886 chore: release version v1.13.9 2024-11-01 10:55:05 +00:00
Dmitry Popov
696c7d2cd1 Map events refactoring (#41)
* refactor(Map): Map event handling refactoring
2024-11-01 14:54:34 +04:00
CI
49c0cb026b chore: release version v1.13.8 2024-10-28 16:21:55 +00:00
158 changed files with 10098 additions and 4744 deletions

View File

@@ -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
RUN apt install -yq curl gnupg mc inotify-tools
RUN apt --fix-broken install 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 RUN mix local.hex --force

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,369 @@
# Change Log # Change Log
<!-- changelog --> <!-- changelog -->
## [v1.29.5](https://github.com/wanderer-industries/wanderer/compare/v1.29.4...v1.29.5) (2024-12-14)
### Bug Fixes:
* Core: Fix character trackers cleanup
## [v1.29.4](https://github.com/wanderer-industries/wanderer/compare/v1.29.3...v1.29.4) (2024-12-10)
### Bug Fixes:
* Core: Small fixes
## [v1.29.3](https://github.com/wanderer-industries/wanderer/compare/v1.29.2...v1.29.3) (2024-12-07)
### Bug Fixes:
* Core: Increased eve DB data download timeout
## [v1.29.2](https://github.com/wanderer-industries/wanderer/compare/v1.29.1...v1.29.2) (2024-12-07)
### Bug Fixes:
* Core: Fix unpkg CDN issues, fix Abyssals sites adding as systems on map
## [v1.29.1](https://github.com/wanderer-industries/wanderer/compare/v1.29.0...v1.29.1) (2024-12-05)
## [v1.29.0](https://github.com/wanderer-industries/wanderer/compare/v1.28.1...v1.29.0) (2024-12-05)
### Features:
* Signatures: Show 'Unsplashed' signatures on the map (optionally)
## [v1.28.1](https://github.com/wanderer-industries/wanderer/compare/v1.28.0...v1.28.1) (2024-12-04)
## [v1.28.0](https://github.com/wanderer-industries/wanderer/compare/v1.27.1...v1.28.0) (2024-12-04)
### Features:
* Map: Added an option to show 'Offline characters' to map admins & managers only
## [v1.27.1](https://github.com/wanderer-industries/wanderer/compare/v1.27.0...v1.27.1) (2024-12-04)
### Bug Fixes:
* Map: Fix 'On the map' visibility
## [v1.27.0](https://github.com/wanderer-industries/wanderer/compare/v1.26.1...v1.27.0) (2024-12-03)
### Features:
* Map: Hide 'On the map' list for 'Viewer' role
## [v1.26.1](https://github.com/wanderer-industries/wanderer/compare/v1.26.0...v1.26.1) (2024-12-03)
### Bug Fixes:
* Signatures: Fix error on splash wh
## [v1.26.0](https://github.com/wanderer-industries/wanderer/compare/v1.25.2...v1.26.0) (2024-12-03)
### Features:
* Signatures: Keep 'Lazy delete' enabled setting
## [v1.25.2](https://github.com/wanderer-industries/wanderer/compare/v1.25.1...v1.25.2) (2024-12-01)
### Bug Fixes:
* Signatures: Fix lazy delete on system switch
## [v1.25.1](https://github.com/wanderer-industries/wanderer/compare/v1.25.0...v1.25.1) (2024-11-28)
### Bug Fixes:
* Signatures: Fix colors & add 'Backspace' hotkey to delete signatures
## [v1.25.0](https://github.com/wanderer-industries/wanderer/compare/v1.24.2...v1.25.0) (2024-11-28)
### Features:
* Signatures: Automatically remove signature if linked system removed
## [v1.24.2](https://github.com/wanderer-industries/wanderer/compare/v1.24.1...v1.24.2) (2024-11-27)
### Bug Fixes:
* Signatures: Fix paste signatures
## [v1.24.1](https://github.com/wanderer-industries/wanderer/compare/v1.24.0...v1.24.1) (2024-11-27)
## [v1.24.0](https://github.com/wanderer-industries/wanderer/compare/v1.23.0...v1.24.0) (2024-11-27)
### Features:
* Signatures: Added "Lazy delete" option & got rid of update popup
## [v1.23.0](https://github.com/wanderer-industries/wanderer/compare/v1.22.0...v1.23.0) (2024-11-26)
### Features:
* Map: Lock systems available to manager/admin roles only (#75)
* Map: Lock systems available to manager/admin roles only
* Map: Fix add system & add acl member select behaviour
## [v1.22.0](https://github.com/wanderer-industries/wanderer/compare/v1.21.0...v1.22.0) (2024-11-26)
### Features:
* Map: Rework design of checkboxes in Signatures settings dialog. Rework design of checkboxes in Routes settings dialog. Now signature will deleteing by Delete hotkey was Backspace. Fixed size of group column in signatures list. Instead Updated column will be Added, updated may be turn on in settings. (#76)
## [v1.21.0](https://github.com/wanderer-industries/wanderer/compare/v1.20.1...v1.21.0) (2024-11-24)
### Features:
* Map: add new gate design, change EOL placement
## [v1.20.1](https://github.com/wanderer-industries/wanderer/compare/v1.20.0...v1.20.1) (2024-11-22)
## [v1.20.0](https://github.com/wanderer-industries/wanderer/compare/v1.19.3...v1.20.0) (2024-11-22)
### Features:
* Core: Add connection type for Gates, add new Update logic
## [v1.19.3](https://github.com/wanderer-industries/wanderer/compare/v1.19.2...v1.19.3) (2024-11-20)
### 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) ## [v1.13.0](https://github.com/wanderer-industries/wanderer/compare/v1.12.11...v1.13.0) (2024-10-28)

View File

@@ -20,11 +20,11 @@ Interested to learn more? [Check more on our website](https://wanderer.ltd/news)
Wanderer is open source project and we have a free as in beer and self-hosted solution called [Wanderer Community Edition (CE)](https://wanderer.ltd/news/community-edition). Here are the differences between Wanderer and Wanderer CE: Wanderer 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 | | | Wanderer Cloud | Wanderer Community Edition |
| ------------- | ------------- | ------------- | | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Infrastructure management** | Easy and convenient. It takes 2 minutes to register your character and create a map. We manage everything so you dont have to worry about anything and can focus on gameplay. | You do it all yourself. You need to get a server and you need to manage your infrastructure. You are responsible for installation, maintenance, upgrades, server capacity, uptime, backup, security, stability, consistency, loading time and so on.| | **Infrastructure management** | Easy and convenient. It takes 2 minutes to register your character and create a map. We manage everything so you dont have to worry about anything and can focus on gameplay. | You do it all yourself. You need to get a server and you need to manage your infrastructure. You are responsible for installation, maintenance, upgrades, server capacity, uptime, backup, security, stability, consistency, loading time and so on. |
| **Release schedule** | Continuously developed and improved with new features and updates multiple times per week. | Latest features and improvements won't be immediately available.| | **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.| | **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/). 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 #### Using .devcontainer
- Run 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 #### Using nix flakes

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,6 +14,9 @@ export const useSystemInfo = ({ systemId }: UseSystemInfoProps) => {
const { systems: systemStatics } = useLoadSystemStatic({ systems: [systemId] }); const { systems: systemStatics } = useLoadSystemStatic({ systems: [systemId] });
// eslint-disable-next-line no-console
console.log('JOipP', `systemStatics`, systemStatics);
return useMemo(() => { return useMemo(() => {
const staticInfo = systemStatics.get(parseInt(systemId)); const staticInfo = systemStatics.get(parseInt(systemId));
const dynamicInfo = getSystemById(systems, systemId); const dynamicInfo = getSystemById(systems, systemId);

View File

@@ -2,3 +2,7 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.BackgroundAlternateColor {
}

View File

@@ -1,19 +1,17 @@
import { ForwardedRef, forwardRef, MouseEvent, useCallback, useEffect, useRef } from 'react'; import { ForwardedRef, forwardRef, MouseEvent, useCallback, useEffect } from 'react';
import ReactFlow, { import ReactFlow, {
Background, Background,
ConnectionMode, ConnectionMode,
Edge, Edge,
MiniMap, MiniMap,
Node, Node,
NodeChange,
NodeDragHandler, NodeDragHandler,
OnConnect, OnConnect,
OnMoveEnd, OnMoveEnd,
OnSelectionChangeFunc, OnSelectionChangeFunc,
SelectionDragHandler, SelectionDragHandler,
SelectionMode, SelectionMode,
useEdgesState,
useNodesState,
NodeChange,
useReactFlow, useReactFlow,
} from 'reactflow'; } from 'reactflow';
import 'reactflow/dist/style.css'; import 'reactflow/dist/style.css';
@@ -21,8 +19,7 @@ import classes from './Map.module.scss';
import './styles/neon-theme.scss'; import './styles/neon-theme.scss';
import './styles/eve-common.scss'; import './styles/eve-common.scss';
import { MapProvider, useMapState } from './MapProvider'; import { MapProvider, useMapState } from './MapProvider';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; import { useNodesState, useEdgesState, useMapHandlers, useUpdateNodes } from './hooks';
import { useMapHandlers, useUpdateNodes } from './hooks';
import { MapHandlers, OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts'; import { MapHandlers, OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts';
import { import {
ContextMenuConnection, ContextMenuConnection,
@@ -37,7 +34,7 @@ import { SESSION_KEY } from '@/hooks/Mapper/constants.ts';
import { SolarSystemConnection, SolarSystemRawType } from '@/hooks/Mapper/types'; import { SolarSystemConnection, SolarSystemRawType } from '@/hooks/Mapper/types';
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts'; import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
import { NodeSelectionMouseHandler } from '@/hooks/Mapper/components/contexts/types.ts'; import { NodeSelectionMouseHandler } from '@/hooks/Mapper/components/contexts/types.ts';
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks'; import clsx from 'clsx';
const DEFAULT_VIEW_PORT = { zoom: 1, x: 0, y: 0 }; const DEFAULT_VIEW_PORT = { zoom: 1, x: 0, y: 0 };
@@ -93,12 +90,16 @@ interface MapCompProps {
refn: ForwardedRef<MapHandlers>; refn: ForwardedRef<MapHandlers>;
onCommand: OutCommandHandler; onCommand: OutCommandHandler;
onSelectionChange: OnMapSelectionChange; onSelectionChange: OnMapSelectionChange;
onManualDelete(systems: string[]): void;
onConnectionInfoClick?(e: SolarSystemConnection): void; onConnectionInfoClick?(e: SolarSystemConnection): void;
onSelectionContextMenu?: NodeSelectionMouseHandler; onSelectionContextMenu?: NodeSelectionMouseHandler;
minimapClasses?: string; minimapClasses?: string;
isShowMinimap?: boolean; isShowMinimap?: boolean;
onSystemContextMenu: (event: MouseEvent<Element>, systemId: string) => void; onSystemContextMenu: (event: MouseEvent<Element>, systemId: string) => void;
showKSpaceBG?: boolean; showKSpaceBG?: boolean;
isThickConnections?: boolean;
isShowBackgroundPattern?: boolean;
isSoftBackground?: boolean;
} }
const MapComp = ({ const MapComp = ({
@@ -109,26 +110,22 @@ const MapComp = ({
onSystemContextMenu, onSystemContextMenu,
onConnectionInfoClick, onConnectionInfoClick,
onSelectionContextMenu, onSelectionContextMenu,
onManualDelete,
isShowMinimap, isShowMinimap,
showKSpaceBG, showKSpaceBG,
isThickConnections,
isShowBackgroundPattern,
isSoftBackground,
}: MapCompProps) => { }: MapCompProps) => {
const { getNode } = useReactFlow(); const { getNode } = useReactFlow();
const [nodes, , onNodesChange] = useNodesState<SolarSystemRawType>(initialNodes); const [nodes, , onNodesChange] = useNodesState<Node<SolarSystemRawType>>(initialNodes);
const [edges, , onEdgesChange] = useEdgesState<Edge<SolarSystemConnection>[]>(initialEdges); const [edges, , onEdgesChange] = useEdgesState<Edge<SolarSystemConnection>>(initialEdges);
useMapHandlers(refn, onSelectionChange); useMapHandlers(refn, onSelectionChange);
useUpdateNodes(nodes); useUpdateNodes(nodes);
const { handleRootContext, ...rootCtxProps } = useContextMenuRootHandlers(); const { handleRootContext, ...rootCtxProps } = useContextMenuRootHandlers();
const { handleConnectionContext, ...connectionCtxProps } = useContextMenuConnectionHandlers(); const { handleConnectionContext, ...connectionCtxProps } = useContextMenuConnectionHandlers();
const { update } = useMapState(); const { update } = useMapState();
const {
data: { systems },
} = useMapRootState();
const { deleteSystems } = useDeleteSystems();
const systemsRef = useRef({ systems });
systemsRef.current = { systems };
const onConnect: OnConnect = useCallback( const onConnect: OnConnect = useCallback(
params => { params => {
@@ -186,39 +183,45 @@ const MapComp = ({
const handleNodesChange = useCallback( const handleNodesChange = useCallback(
(changes: NodeChange[]) => { (changes: NodeChange[]) => {
const systemsIdsToRemove: string[] = []; const systemsIdsToRemove: string[] = [];
const nextChanges = changes.reduce((acc, change) => { const nextChanges = changes.reduce((acc, change) => {
if (change.type === 'remove') { if (change.type !== 'remove') {
const node = getNode(change.id); return [...acc, change];
const { systems = [] } = systemsRef.current; }
if (node?.data?.id && !systems.map(s => s.id).includes(node?.data?.id)) {
return [...acc, change]; const node = getNode(change.id);
} else if (!node?.data?.locked) { if (!node) {
systemsIdsToRemove.push(node?.data?.id); return [...acc, change];
} }
if (node.data.locked) {
return acc; return acc;
} }
systemsIdsToRemove.push(node.data.id);
return [...acc, change]; return [...acc, change];
}, [] as NodeChange[]); }, [] as NodeChange[]);
if (systemsIdsToRemove.length) { if (systemsIdsToRemove.length > 0) {
deleteSystems(systemsIdsToRemove); onManualDelete(systemsIdsToRemove);
} }
onNodesChange(nextChanges); onNodesChange(nextChanges);
}, },
[deleteSystems, getNode, onNodesChange], [getNode, onManualDelete, onNodesChange],
); );
useEffect(() => { useEffect(() => {
update(x => ({ update(x => ({
...x, ...x,
showKSpaceBG: showKSpaceBG, showKSpaceBG: showKSpaceBG,
isThickConnections: isThickConnections,
})); }));
}, [showKSpaceBG, update]); }, [showKSpaceBG, isThickConnections, update]);
return ( return (
<> <>
<div className={classes.MapRoot}> <div className={clsx(classes.MapRoot, { ['bg-neutral-900']: isSoftBackground })}>
<ReactFlow <ReactFlow
nodes={nodes} nodes={nodes}
edges={edges} edges={edges}
@@ -238,6 +241,7 @@ const MapComp = ({
onConnectStart={() => update({ isConnecting: true })} onConnectStart={() => update({ isConnecting: true })}
onConnectEnd={() => update({ isConnecting: false })} onConnectEnd={() => update({ isConnecting: false })}
onNodeMouseEnter={(_, node) => update({ hoverNodeId: node.id })} onNodeMouseEnter={(_, node) => update({ hoverNodeId: node.id })}
// onKeyUp=
onNodeMouseLeave={() => update({ hoverNodeId: null })} onNodeMouseLeave={() => update({ hoverNodeId: null })}
onEdgeClick={(_, t) => { onEdgeClick={(_, t) => {
onConnectionInfoClick?.(t.data); onConnectionInfoClick?.(t.data);
@@ -264,7 +268,7 @@ const MapComp = ({
selectionMode={SelectionMode.Partial} selectionMode={SelectionMode.Partial}
> >
{isShowMinimap && <MiniMap pannable zoomable ariaLabel="Mini map" className={minimapClasses} />} {isShowMinimap && <MiniMap pannable zoomable ariaLabel="Mini map" className={minimapClasses} />}
<Background /> {isShowBackgroundPattern && <Background />}
</ReactFlow> </ReactFlow>
{/* <button className="z-auto btn btn-primary absolute top-20 right-20" onClick={handleGetPassages}> {/* <button className="z-auto btn btn-primary absolute top-20 right-20" onClick={handleGetPassages}>
Test // DON NOT REMOVE Test // DON NOT REMOVE

View File

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

View File

@@ -3,7 +3,7 @@ import { ContextMenu } from 'primereact/contextmenu';
import { PrimeIcons } from 'primereact/api'; import { PrimeIcons } from 'primereact/api';
import { MenuItem } from 'primereact/menuitem'; import { MenuItem } from 'primereact/menuitem';
import { Edge } from '@reactflow/core/dist/esm/types/edges'; import { Edge } from '@reactflow/core/dist/esm/types/edges';
import { MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types'; import { ConnectionType, MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
import clsx from 'clsx'; import clsx from 'clsx';
import classes from './ContextMenuConnection.module.scss'; import classes from './ContextMenuConnection.module.scss';
import { MASS_STATE_NAMES, MASS_STATE_NAMES_ORDER } from '@/hooks/Mapper/components/map/constants.ts'; import { MASS_STATE_NAMES, MASS_STATE_NAMES_ORDER } from '@/hooks/Mapper/components/map/constants.ts';
@@ -35,36 +35,49 @@ export const ContextMenuConnection: React.FC<ContextMenuConnectionProps> = ({
} }
const isFrigateSize = edge.data?.ship_size_type === ShipSizeStatus.small; const isFrigateSize = edge.data?.ship_size_type === ShipSizeStatus.small;
const isWormhole = edge.data?.type !== ConnectionType.gate;
return [ return [
{ ...(isWormhole
label: `EOL`, ? [
className: clsx({ {
[classes.ConnectionTimeEOL]: edge.data?.time_status === TimeStatus.eol, label: `EOL`,
}), className: clsx({
icon: PrimeIcons.CLOCK, [classes.ConnectionTimeEOL]: edge.data?.time_status === TimeStatus.eol,
command: onChangeTimeState, }),
}, icon: PrimeIcons.CLOCK,
{ command: onChangeTimeState,
label: `Frigate`, },
className: clsx({ ]
[classes.ConnectionFrigate]: isFrigateSize, : []),
}), ...(isWormhole
icon: PrimeIcons.CLOUD, ? [
command: () => {
onChangeShipSizeStatus( label: `Frigate`,
edge.data?.ship_size_type === ShipSizeStatus.small ? ShipSizeStatus.normal : ShipSizeStatus.small, className: clsx({
), [classes.ConnectionFrigate]: isFrigateSize,
}, }),
{ icon: PrimeIcons.CLOUD,
label: `Save mass`, command: () =>
className: clsx({ onChangeShipSizeStatus(
[classes.ConnectionSave]: edge.data?.locked, edge.data?.ship_size_type === ShipSizeStatus.small ? ShipSizeStatus.normal : ShipSizeStatus.small,
}), ),
icon: PrimeIcons.LOCK, },
command: () => onToggleMassSave(!edge.data?.locked), ]
}, : []),
...(!isFrigateSize ...(isWormhole
? [
{
label: `Save mass`,
className: clsx({
[classes.ConnectionSave]: edge.data?.locked,
}),
icon: PrimeIcons.LOCK,
command: () => onToggleMassSave(!edge.data?.locked),
},
]
: []),
...(isWormhole && !isFrigateSize
? [ ? [
{ {
label: `Mass status`, label: `Mass status`,

View File

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

View File

@@ -21,7 +21,11 @@
} }
&.Frigate { &.Frigate {
stroke: #4e62c9; stroke: #d4f0ff;
}
&.Gate {
stroke: #1c1e15;
} }
&.Hovered { &.Hovered {
@@ -37,9 +41,16 @@
} }
&.Frigate { &.Frigate {
stroke: #41acd7; stroke: #d4f0ff;
} }
}
&.Tick {
stroke-width: 3px;
&.Hovered {
stroke-width: 3px;
}
} }
} }
@@ -61,6 +72,19 @@
stroke: #ef7dce; stroke: #ef7dce;
} }
} }
&.Tick {
stroke-width: 5px;
&.TimeCrit {
stroke-width: 6px;
}
}
&.Gate {
stroke: #9aff40;
}
} }
.ClickPath { .ClickPath {
@@ -93,5 +117,14 @@
width: 5px; width: 5px;
height: 5px; height: 5px;
z-index: 1001; z-index: 1001;
&.Tick {
width: 7px;
height: 7px;
&.Right {
margin-left: 0px;
}
}
} }

View File

@@ -1,39 +1,64 @@
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import classes from './SolarSystemEdge.module.scss'; import classes from './SolarSystemEdge.module.scss';
import { EdgeLabelRenderer, EdgeProps, getBezierPath, Position, useStore } from 'reactflow'; import { EdgeLabelRenderer, EdgeProps, getBezierPath, getSmoothStepPath, Position, useStore } from 'reactflow';
import { getEdgeParams } from '@/hooks/Mapper/components/map/utils.ts'; import { getEdgeParams } from '@/hooks/Mapper/components/map/utils.ts';
import clsx from 'clsx'; import clsx from 'clsx';
import { MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types'; import { ConnectionType, MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types';
import { PrimeIcons } from 'primereact/api'; import { PrimeIcons } from 'primereact/api';
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper'; import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
const MAP_TRANSLATES: Record<string, string> = { const MAP_TRANSLATES: Record<string, string> = {
[Position.Top]: 'translate(-50%, 0%)', [Position.Top]: 'translate(-48%, 0%)',
[Position.Bottom]: 'translate(-50%, -100%)', [Position.Bottom]: 'translate(-50%, -100%)',
[Position.Left]: 'translate(0%, -50%)', [Position.Left]: 'translate(0%, -50%)',
[Position.Right]: 'translate(-100%, -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>) => { export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }: EdgeProps<SolarSystemConnection>) => {
const sourceNode = useStore(useCallback(store => store.nodeInternals.get(source), [source])); const sourceNode = useStore(useCallback(store => store.nodeInternals.get(source), [source]));
const targetNode = useStore(useCallback(store => store.nodeInternals.get(target), [target])); const targetNode = useStore(useCallback(store => store.nodeInternals.get(target), [target]));
const isWormhole = data?.type !== ConnectionType.gate;
const {
data: { isThickConnections },
} = useMapState();
const [hovered, setHovered] = useState(false); const [hovered, setHovered] = useState(false);
const [path, labelX, labelY, sx, sy, tx, ty, sourcePos, targetPos] = useMemo(() => { const [path, labelX, labelY, sx, sy, tx, ty, sourcePos, targetPos] = useMemo(() => {
const { sx, sy, tx, ty, sourcePos, targetPos } = getEdgeParams(sourceNode, targetNode); const { sx, sy, tx, ty, sourcePos, targetPos } = getEdgeParams(sourceNode, targetNode);
const [edgePath, labelX, labelY] = getBezierPath({ const offset = isThickConnections ? MAP_OFFSETS_TICK[targetPos] : MAP_OFFSETS[targetPos];
sourceX: sx,
sourceY: sy, const method = isWormhole ? getBezierPath : getSmoothStepPath;
const [edgePath, labelX, labelY] = method({
sourceX: sx - offset.x,
sourceY: sy - offset.y,
sourcePosition: sourcePos, sourcePosition: sourcePos,
targetPosition: targetPos, targetPosition: targetPos,
targetX: tx, targetX: tx + offset.x,
targetY: ty, targetY: ty + offset.y,
}); });
return [edgePath, labelX, labelY, sx, sy, tx, ty, sourcePos, targetPos]; return [edgePath, labelX, labelY, sx, sy, tx, ty, sourcePos, targetPos];
}, [sourceNode, targetNode]); }, [isThickConnections, sourceNode, targetNode, isWormhole]);
if (!sourceNode || !targetNode || !data) { if (!sourceNode || !targetNode || !data) {
return null; return null;
@@ -44,8 +69,10 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
<path <path
id={`back_${id}`} id={`back_${id}`}
className={clsx(classes.EdgePathBack, { className={clsx(classes.EdgePathBack, {
[classes.TimeCrit]: data.time_status === TimeStatus.eol, [classes.Tick]: isThickConnections,
[classes.TimeCrit]: isWormhole && data.time_status === TimeStatus.eol,
[classes.Hovered]: hovered, [classes.Hovered]: hovered,
[classes.Gate]: !isWormhole,
})} })}
d={path} d={path}
markerEnd={markerEnd} markerEnd={markerEnd}
@@ -54,10 +81,12 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
<path <path
id={`front_${id}`} id={`front_${id}`}
className={clsx(classes.EdgePathFront, { className={clsx(classes.EdgePathFront, {
[classes.Tick]: isThickConnections,
[classes.Hovered]: hovered, [classes.Hovered]: hovered,
[classes.MassVerge]: data.mass_status === MassState.verge, [classes.MassVerge]: isWormhole && data.mass_status === MassState.verge,
[classes.MassHalf]: data.mass_status === MassState.half, [classes.MassHalf]: isWormhole && data.mass_status === MassState.half,
[classes.Frigate]: data.ship_size_type === ShipSizeStatus.small, [classes.Frigate]: isWormhole && data.ship_size_type === ShipSizeStatus.small,
[classes.Gate]: !isWormhole,
})} })}
d={path} d={path}
markerEnd={markerEnd} markerEnd={markerEnd}
@@ -75,11 +104,19 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
<EdgeLabelRenderer> <EdgeLabelRenderer>
<div <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)` }} style={{ transform: `${MAP_TRANSLATES[sourcePos]} translate(${sx}px,${sy}px)` }}
/> />
<div <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)` }} style={{ transform: `${MAP_TRANSLATES[targetPos]} translate(${tx}px,${ty}px)` }}
/> />
@@ -89,7 +126,7 @@ export const SolarSystemEdge = ({ id, source, target, markerEnd, style, data }:
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`, transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
}} }}
> >
{data.locked && ( {isWormhole && data.locked && (
<WdTooltipWrapper <WdTooltipWrapper
content="Save mass" content="Save mass"
className={clsx( className={clsx(

View File

@@ -6,7 +6,7 @@ $pastel-green: #88b04b;
$pastel-yellow: #ffdd59; $pastel-yellow: #ffdd59;
$dark-bg: #2d2d2d; $dark-bg: #2d2d2d;
$text-color: #ffffff; $text-color: #ffffff;
$tooltip-bg: #202020; // Темный фон для подсказок $tooltip-bg: #202020; // Dark background for tooltips
.RootCustomNode { .RootCustomNode {
display: flex; display: flex;
@@ -136,7 +136,7 @@ $tooltip-bg: #202020; // Темный фон для подсказок
.Bookmarks { .Bookmarks {
position: absolute; position: absolute;
width: 100%; width: 100%;
z-index: 0; z-index: 1;
display: flex; display: flex;
left: 4px; left: 4px;
@@ -182,6 +182,42 @@ $tooltip-bg: #202020; // Темный фон для подсказок
} }
} }
.Unsplashed {
position: absolute;
width: calc(50% - 4px);
z-index: -1;
display: flex;
flex-wrap: wrap;
gap: 2px;
left: 2px;
&--right {
left: calc(50% + 6px);
}
& > .Signature {
width: 13px;
height: 4px;
position: relative;
top: 3px;
border-radius: 5px;
color: #ffffff;
font-size: 8px;
text-align: center;
padding-top: 2px;
font-weight: bolder;
padding-left: 3px;
padding-right: 3px;
display: block;
background-color: #833ca4;
&:not(:first-child) {
box-shadow: inset 4px -3px 4px rgba(0, 0, 0, 0.3);
}
}
}
.icon { .icon {
width: 8px; width: 8px;
height: 8px; height: 8px;
@@ -212,6 +248,14 @@ $tooltip-bg: #202020; // Темный фон для подсказок
color: #ffb01d; color: #ffb01d;
} }
/* Firefox kostyl */
@-moz-document url-prefix() {
.classSystemName {
font-family: inherit !important;
font-weight: bold;
}
}
.classSystemName { .classSystemName {
//font-weight: bold; //font-weight: bold;
} }
@@ -262,6 +306,12 @@ $tooltip-bg: #202020; // Темный фон для подсказок
& > * { & > * {
line-height: 10px; line-height: 10px;
} }
/* Firefox kostyl */
@-moz-document url-prefix() {
position: relative;
top: -1px;
}
} }
.Handlers { .Handlers {
@@ -299,4 +349,25 @@ $tooltip-bg: #202020; // Темный фон для подсказок
&.HandleLeft { &.HandleLeft {
left: -2px; left: -2px;
} }
&.Tick {
width: 7px;
height: 7px;
&.HandleTop {
top: -3px;
}
&.HandleRight {
right: -3px;
}
&.HandleBottom {
bottom: -3px;
}
&.HandleLeft {
left: -3px;
}
}
} }

View File

@@ -3,6 +3,8 @@ import { Handle, Position, WrapNodeProps } from 'reactflow';
import { MapSolarSystemType } from '../../map.types'; import { MapSolarSystemType } from '../../map.types';
import classes from './SolarSystemNode.module.scss'; import classes from './SolarSystemNode.module.scss';
import clsx from 'clsx'; import clsx from 'clsx';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { import {
EFFECT_BACKGROUND_STYLES, EFFECT_BACKGROUND_STYLES,
LABELS_INFO, LABELS_INFO,
@@ -12,8 +14,9 @@ import {
} from '@/hooks/Mapper/components/map/constants.ts'; } from '@/hooks/Mapper/components/map/constants.ts';
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace.ts'; import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace.ts';
import { WormholeClassComp } from '@/hooks/Mapper/components/map/components/WormholeClassComp'; import { WormholeClassComp } from '@/hooks/Mapper/components/map/components/WormholeClassComp';
import { UnsplashedSignature } from '@/hooks/Mapper/components/map/components/UnsplashedSignature';
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx'; import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
import { getSystemClassStyles } from '@/hooks/Mapper/components/map/helpers'; import { getSystemClassStyles, prepareUnsplashedChunks } from '@/hooks/Mapper/components/map/helpers';
import { sortWHClasses } from '@/hooks/Mapper/helpers'; import { sortWHClasses } from '@/hooks/Mapper/helpers';
import { PrimeIcons } from 'primereact/api'; import { PrimeIcons } from 'primereact/api';
import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager.ts'; import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager.ts';
@@ -50,6 +53,9 @@ export const getActivityType = (count: number) => {
// eslint-disable-next-line react/display-name // eslint-disable-next-line react/display-name
export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarSystemType>) => { export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarSystemType>) => {
const { interfaceSettings } = useMapRootState();
const { isShowUnsplashedSignatures } = interfaceSettings;
const { const {
system_class, system_class,
security, security,
@@ -63,6 +69,8 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
solar_system_name, solar_system_name,
} = data.system_static_info; } = data.system_static_info;
const signatures = data.system_signatures;
const { locked, name, tag, status, labels, id } = data || {}; const { locked, name, tag, status, labels, id } = data || {};
const customName = solar_system_name !== name ? name : undefined; const customName = solar_system_name !== name ? name : undefined;
@@ -79,6 +87,7 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
hoverNodeId, hoverNodeId,
visibleNodes, visibleNodes,
showKSpaceBG, showKSpaceBG,
isThickConnections,
}, },
outCommand, outCommand,
} = useMapState(); } = useMapState();
@@ -127,6 +136,22 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
const space = showKSpaceBG ? REGIONS_MAP[region_id] : ''; const space = showKSpaceBG ? REGIONS_MAP[region_id] : '';
const regionClass = showKSpaceBG ? SpaceToClass[space] : null; const regionClass = showKSpaceBG ? SpaceToClass[space] : null;
const [unsplashedLeft, unsplashedRight] = useMemo(() => {
if (!isShowUnsplashedSignatures) {
return [[], []];
}
return prepareUnsplashedChunks(
signatures
.filter(s => s.group === 'Wormhole' && !s.linked_system)
.map(s => ({
eve_id: s.eve_id,
type: s.type,
custom_info: s.custom_info,
})),
);
}, [isShowUnsplashedSignatures, signatures]);
return ( return (
<> <>
{visible && ( {visible && (
@@ -236,31 +261,59 @@ export const SolarSystemNode = memo(({ data, selected }: WrapNodeProps<MapSolarS
)} )}
</div> </div>
{visible && isShowUnsplashedSignatures && (
<div className={classes.Unsplashed}>
{unsplashedLeft.map(x => (
<UnsplashedSignature key={x.sig_id} signature={x} />
))}
</div>
)}
{visible && isShowUnsplashedSignatures && (
<div className={clsx([classes.Unsplashed, classes['Unsplashed--right']])}>
{unsplashedRight.map(x => (
<UnsplashedSignature key={x.sig_id} signature={x} />
))}
</div>
)}
<div onMouseDownCapture={dbClick} className={classes.Handlers}> <div onMouseDownCapture={dbClick} className={classes.Handlers}>
<Handle <Handle
type="source" 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' }} style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
position={Position.Top} position={Position.Top}
id="a" id="a"
/> />
<Handle <Handle
type="source" 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' }} style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
position={Position.Right} position={Position.Right}
id="b" id="b"
/> />
<Handle <Handle
type="source" 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' }} style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
position={Position.Bottom} position={Position.Bottom}
id="c" id="c"
/> />
<Handle <Handle
type="source" 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' }} style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
position={Position.Left} position={Position.Left}
id="d" id="d"

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
import { MassState } from '@/hooks/Mapper/types'; import { ConnectionType, MassState } from '@/hooks/Mapper/types';
export enum SOLAR_SYSTEM_CLASS_IDS { export enum SOLAR_SYSTEM_CLASS_IDS {
ccp1 = -1, ccp1 = -1,
@@ -712,6 +712,13 @@ export const STATUS_CLASSES: Record<number, string> = {
[STATUSES.dangerous]: 'eve-system-status-dangerous', [STATUSES.dangerous]: 'eve-system-status-dangerous',
}; };
export const TYPE_NAMES_ORDER = [ConnectionType.wormhole, ConnectionType.gate];
export const TYPE_NAMES = {
[ConnectionType.wormhole]: 'Wormhole',
[ConnectionType.gate]: 'Gate',
};
export const MASS_STATE_NAMES_ORDER = [MassState.verge, MassState.half, MassState.normal]; export const MASS_STATE_NAMES_ORDER = [MassState.verge, MassState.half, MassState.normal];
export const MASS_STATE_NAMES = { export const MASS_STATE_NAMES = {

View File

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

View File

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

View File

@@ -12,6 +12,7 @@ export const useMapAddSystems = () => {
return useCallback((systems: CommandAddSystems) => { return useCallback((systems: CommandAddSystems) => {
const { rf } = ref.current; const { rf } = ref.current;
const nodes = rf.getNodes(); const nodes = rf.getNodes();
const prepared: Node[] = systems.filter(x => !nodes.some(y => x.id === y.id)).map(convertSystem2Node); const prepared: Node[] = systems.filter(x => !nodes.some(y => x.id === y.id)).map(convertSystem2Node);
rf.addNodes(prepared); rf.addNodes(prepared);
}, []); }, []);

View File

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

View File

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

View File

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

View File

@@ -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];
}

View 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[];
}

View File

@@ -48,6 +48,7 @@ const restoreWindowsFromLS = (): WidgetGridItem[] => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const raw = localStorage.getItem(SESSION_KEY.windows); const raw = localStorage.getItem(SESSION_KEY.windows);
if (!raw) { if (!raw) {
console.warn('No windows found in local storage!!');
return DEFAULT_WINDOWS; return DEFAULT_WINDOWS;
} }
@@ -63,7 +64,7 @@ const restoreWindowsFromLS = (): WidgetGridItem[] => {
}; };
export const MapInterface = () => { export const MapInterface = () => {
const [items, setItems] = useState<WidgetGridItem[]>(restoreWindowsFromLS()); const [items, setItems] = useState<WidgetGridItem[]>(restoreWindowsFromLS);
return ( return (
<WidgetsGrid <WidgetsGrid

View File

@@ -6,17 +6,23 @@ import { SystemSignature } from '@/hooks/Mapper/types';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { CommandLinkSignatureToSystem } from '@/hooks/Mapper/types'; import { CommandLinkSignatureToSystem } from '@/hooks/Mapper/types';
import { SystemSignaturesContent } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignaturesContent'; import { SystemSignaturesContent } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignaturesContent';
import { SHOW_DESCRIPTION_COLUMN_SETTING } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatures';
import { import {
Setting, Setting,
COSMIC_SIGNATURE, COSMIC_SIGNATURE,
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureSettingsDialog'; } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureSettingsDialog';
import { SignatureGroup } from '@/hooks/Mapper/types';
interface SystemLinkSignatureDialogProps { interface SystemLinkSignatureDialogProps {
data: CommandLinkSignatureToSystem; data: CommandLinkSignatureToSystem;
setVisible: (visible: boolean) => void; setVisible: (visible: boolean) => void;
} }
const signatureSettings: Setting[] = [{ key: COSMIC_SIGNATURE, name: 'Show Cosmic Signatures', value: true }]; const signatureSettings: Setting[] = [
{ key: COSMIC_SIGNATURE, name: 'Show Cosmic Signatures', value: true },
{ key: SignatureGroup.Wormhole, name: 'Wormhole', value: true },
{ key: SHOW_DESCRIPTION_COLUMN_SETTING, name: 'Show Description Column', value: true, isFilter: false },
];
export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignatureDialogProps) => { export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignatureDialogProps) => {
const { outCommand } = useMapRootState(); const { outCommand } = useMapRootState();
@@ -59,6 +65,7 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat
> >
<SystemSignaturesContent <SystemSignaturesContent
systemId={`${data.solar_system_source}`} systemId={`${data.solar_system_source}`}
hideLinkedSignatures
settings={signatureSettings} settings={signatureSettings}
onSelect={handleSelect} onSelect={handleSelect}
selectable={true} selectable={true}

View File

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

View File

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

View File

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

View File

@@ -19,6 +19,8 @@ import { PrimeIcons } from 'primereact/api';
import { RoutesSettingsDialog } from './RoutesSettingsDialog'; import { RoutesSettingsDialog } from './RoutesSettingsDialog';
import { RoutesProvider, useRouteProvider } from './RoutesProvider.tsx'; import { RoutesProvider, useRouteProvider } from './RoutesProvider.tsx';
import { ContextMenuSystemInfo, useContextMenuSystemInfoHandlers } from '@/hooks/Mapper/components/contexts'; import { ContextMenuSystemInfo, useContextMenuSystemInfoHandlers } from '@/hooks/Mapper/components/contexts';
import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth.ts';
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
const sortByDist = (a: Route, b: Route) => { const sortByDist = (a: Route, b: Route) => {
const distA = a.has_connection ? a.systems?.length || 0 : Infinity; const distA = a.has_connection ? a.systems?.length || 0 : Infinity;
@@ -30,7 +32,6 @@ const sortByDist = (a: Route, b: Route) => {
export const RoutesWidgetContent = () => { export const RoutesWidgetContent = () => {
const { const {
data: { selectedSystems, hubs = [], systems, routes }, data: { selectedSystems, hubs = [], systems, routes },
mapRef,
outCommand, outCommand,
} = useMapRootState(); } = useMapRootState();
@@ -42,7 +43,6 @@ export const RoutesWidgetContent = () => {
const { open, ...systemCtxProps } = useContextMenuSystemInfoHandlers({ const { open, ...systemCtxProps } = useContextMenuSystemInfoHandlers({
outCommand, outCommand,
hubs, hubs,
mapRef,
}); });
const preparedHubs = useMemo(() => { const preparedHubs = useMemo(() => {
@@ -172,20 +172,25 @@ export const RoutesWidgetComp = () => {
}); });
}, [data, update]); }, [data, update]);
const ref = useRef<HTMLDivElement>(null);
const compact = useMaxWidth(ref, 155);
return ( return (
<Widget <Widget
label={ label={
<div className="flex justify-between items-center text-xs w-full"> <div className="flex justify-between items-center text-xs w-full" ref={ref}>
<span className="select-none">Routes</span> <span className="select-none">Routes</span>
<LayoutEventBlocker className="flex items-center gap-2"> <LayoutEventBlocker className="flex items-center gap-2">
<WdCheckbox <WdTooltipWrapper content="Show shortest route">
size="xs" <WdCheckbox
labelSide="left" size="xs"
label={'Show shortest'} labelSide="left"
value={!isSecure} label={compact ? '' : 'Show shortest'}
onChange={handleSecureChange} value={!isSecure}
classNameLabel={clsx('text-red-400')} onChange={handleSecureChange}
/> classNameLabel={clsx('text-red-400')}
/>
</WdTooltipWrapper>
<WdImgButton className={PrimeIcons.SLIDERS_H} onClick={() => setRouteSettingsVisible(true)} /> <WdImgButton className={PrimeIcons.SLIDERS_H} onClick={() => setRouteSettingsVisible(true)} />
</LayoutEventBlocker> </LayoutEventBlocker>
</div> </div>

View File

@@ -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;
}
}
}

View File

@@ -1,9 +1,11 @@
import { Dialog } from 'primereact/dialog'; import { Dialog } from 'primereact/dialog';
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import { Button } from 'primereact/button'; import { Button } from 'primereact/button';
import { Checkbox } from 'primereact/checkbox'; import { TabPanel, TabView } from 'primereact/tabview';
import styles from './SystemSignatureSettingsDialog.module.scss';
import { PrettySwitchbox } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/components';
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_SIGNATURE = 'Cosmic Signature';
export const COSMIC_ANOMALY = 'Cosmic Anomaly'; export const COSMIC_ANOMALY = 'Cosmic Anomaly';
@@ -24,8 +26,12 @@ export const SystemSignatureSettingsDialog = ({
onSave, onSave,
onCancel, onCancel,
}: SystemSignatureSettingsDialogProps) => { }: SystemSignatureSettingsDialogProps) => {
const [activeIndex, setActiveIndex] = useState(0);
const [settings, setSettings] = useState<Setting[]>(defaultSettings); const [settings, setSettings] = useState<Setting[]>(defaultSettings);
const filterSettings = settings.filter(setting => setting.isFilter);
const userSettings = settings.filter(setting => !setting.isFilter);
const handleSettingsChange = (key: string) => { const handleSettingsChange = (key: string) => {
setSettings(prevState => prevState.map(item => (item.key === key ? { ...item, value: !item.value } : item))); setSettings(prevState => prevState.map(item => (item.key === key ? { ...item, value: !item.value } : item)));
}; };
@@ -35,23 +41,45 @@ export const SystemSignatureSettingsDialog = ({
}, [onSave, settings]); }, [onSave, settings]);
return ( 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 h-[500px]">
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3 justify-between h-full">
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
{settings.map(setting => { <div className={styles.verticalTabsContainer}>
return ( <TabView
<div key={setting.key} className="flex items-center"> activeIndex={activeIndex}
<Checkbox onTabChange={e => setActiveIndex(e.index)}
inputId={setting.key} className={styles.verticalTabView}
checked={setting.value} >
onChange={() => handleSettingsChange(setting.key)} <TabPanel header="Filters" headerClassName={styles.verticalTabHeader}>
/> <div className="w-full h-full flex flex-col gap-1">
<label htmlFor={setting.key} className="ml-2"> {filterSettings.map(setting => {
{setting.name} return (
</label> <PrettySwitchbox
</div> key={setting.key}
); label={setting.name}
})} checked={setting.value}
setChecked={() => handleSettingsChange(setting.key)}
/>
);
})}
</div>
</TabPanel>
<TabPanel header="User Interface" headerClassName={styles.verticalTabHeader}>
<div className="w-full h-full flex flex-col gap-1">
{userSettings.map(setting => {
return (
<PrettySwitchbox
key={setting.key}
label={setting.name}
checked={setting.value}
setChecked={() => handleSettingsChange(setting.key)}
/>
);
})}
</div>
</TabPanel>
</TabView>
</div>
</div> </div>
<div className="flex gap-2 justify-end"> <div className="flex gap-2 justify-end">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,13 +7,13 @@ import { useCallback, useState } from 'react';
import { OnTheMap, RightBar } from '@/hooks/Mapper/components/mapRootContent/components'; import { OnTheMap, RightBar } from '@/hooks/Mapper/components/mapRootContent/components';
import { MapContextMenu } from '@/hooks/Mapper/components/mapRootContent/components/MapContextMenu/MapContextMenu.tsx'; import { MapContextMenu } from '@/hooks/Mapper/components/mapRootContent/components/MapContextMenu/MapContextMenu.tsx';
import { useSkipContextMenu } from '@/hooks/Mapper/hooks/useSkipContextMenu'; import { useSkipContextMenu } from '@/hooks/Mapper/hooks/useSkipContextMenu';
import { MapSettings } from "@/hooks/Mapper/components/mapRootContent/components/MapSettings"; import { MapSettings } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings';
export interface MapRootContentProps {} export interface MapRootContentProps {}
// eslint-disable-next-line no-empty-pattern // eslint-disable-next-line no-empty-pattern
export const MapRootContent = ({}: MapRootContentProps) => { export const MapRootContent = ({}: MapRootContentProps) => {
const { mapRef, interfaceSettings } = useMapRootState(); const { interfaceSettings } = useMapRootState();
const { isShowMenu } = interfaceSettings; const { isShowMenu } = interfaceSettings;
const [showOnTheMap, setShowOnTheMap] = useState(false); const [showOnTheMap, setShowOnTheMap] = useState(false);
@@ -26,7 +26,7 @@ export const MapRootContent = ({}: MapRootContentProps) => {
useSkipContextMenu(); useSkipContextMenu();
return ( return (
<Layout map={<MapWrapper refn={mapRef} />}> <Layout map={<MapWrapper />}>
{!isShowMenu ? ( {!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-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"> <div className="absolute top-0 left-0 w-[calc(100%-3.5rem)] h-full pointer-events-none">

View File

@@ -1,13 +1,22 @@
import classes from './Connections.module.scss'; import classes from './Connections.module.scss';
import { Sidebar } from 'primereact/sidebar'; 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 { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { VirtualScroller, VirtualScrollerTemplateOptions } from 'primereact/virtualscroller'; import { VirtualScroller, VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
import clsx from 'clsx'; import clsx from 'clsx';
import { ConnectionOutput, OutCommand, Passage, SolarSystemConnection } from '@/hooks/Mapper/types'; import {
ConnectionType,
ConnectionOutput,
ConnectionInfoOutput,
OutCommand,
Passage,
SolarSystemConnection,
} from '@/hooks/Mapper/types';
import { PassageCard } from './PassageCard'; import { PassageCard } from './PassageCard';
import { InfoDrawer, SystemView } from '@/hooks/Mapper/components/ui-kit'; import { InfoDrawer, SystemView } from '@/hooks/Mapper/components/ui-kit';
import { kgToTons } from '@/hooks/Mapper/utils/kgToTons.ts'; 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(); const sortByDate = (a: string, b: string) => new Date(a).getTime() - new Date(b).getTime();
@@ -68,26 +77,49 @@ export const Connections = ({ selectedConnection, onHide }: OnTheMapProps) => {
return connections.find(x => x.source === selectedConnection.source && x.target === selectedConnection.target); return connections.find(x => x.source === selectedConnection.source && x.target === selectedConnection.target);
}, [connections, selectedConnection]); }, [connections, selectedConnection]);
const isWormhole = useMemo(() => {
return cnInfo?.type !== ConnectionType.gate;
}, [cnInfo]);
const [passages, setPassages] = useState<Passage[]>([]); const [passages, setPassages] = useState<Passage[]>([]);
const [info, setInfo] = useState<ConnectionInfoOutput | null>(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(() => { useEffect(() => {
if (!selectedConnection) { if (!selectedConnection) {
return; return;
} }
loadInfo(selectedConnection);
const loadInfo = async () => { loadPassages(selectedConnection);
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();
}, [selectedConnection]); }, [selectedConnection]);
const approximateMass = useMemo(() => { const approximateMass = useMemo(() => {
@@ -109,7 +141,7 @@ export const Connections = ({ selectedConnection, onHide }: OnTheMapProps) => {
> >
<div className={clsx(classes.SidebarContent, '')}> <div className={clsx(classes.SidebarContent, '')}>
{/* Connection Info */} {/* Connection Info */}
<div className="px-2 pb-3 flex flex-col gap-2"> <div className="px-2 flex flex-col gap-2">
{/* Connection Info Row */} {/* Connection Info Row */}
<InfoDrawer title="Connection" rightSide> <InfoDrawer title="Connection" rightSide>
<div className="flex justify-end gap-2 items-center"> <div className="flex justify-end gap-2 items-center">
@@ -127,10 +159,25 @@ export const Connections = ({ selectedConnection, onHide }: OnTheMapProps) => {
</div> </div>
</InfoDrawer> </InfoDrawer>
{/* Connection Info Row */} <div className="flex justify-between gap-2">
<InfoDrawer title="Approximate mass of passages" rightSide> {/*Left column*/}
{kgToTons(approximateMass)} <div>
</InfoDrawer> {isWormhole && info?.marl_eol_time && (
<InfoDrawer title="Mark EOL Time">
<TimeAgo timestamp={info.marl_eol_time} />
</InfoDrawer>
)}
</div>
{/*Right column*/}
<div>
{isWormhole && (
<InfoDrawer title="Approximate mass of passages" rightSide>
{kgToTons(approximateMass)}
</InfoDrawer>
)}
</div>
</div>
<div className="flex gap-2"></div> <div className="flex gap-2"></div>
</div> </div>

View File

@@ -5,6 +5,8 @@ import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrap
import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit'; import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit';
import { OutCommand } from '@/hooks/Mapper/types'; import { OutCommand } from '@/hooks/Mapper/types';
import { MenuItem } from 'primereact/menuitem'; import { MenuItem } from 'primereact/menuitem';
import { useMapCheckPermissions } from '@/hooks/Mapper/mapRootProvider/hooks/api';
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
export interface MapContextMenuProps { export interface MapContextMenuProps {
onShowOnTheMap?: () => void; onShowOnTheMap?: () => void;
@@ -14,6 +16,8 @@ export interface MapContextMenuProps {
export const MapContextMenu = ({ onShowOnTheMap, onShowMapSettings }: MapContextMenuProps) => { export const MapContextMenu = ({ onShowOnTheMap, onShowMapSettings }: MapContextMenuProps) => {
const { outCommand, setInterfaceSettings } = useMapRootState(); const { outCommand, setInterfaceSettings } = useMapRootState();
const canTrackCharacters = useMapCheckPermissions([UserPermission.TRACK_CHARACTER]);
const menuRight = useRef<Menu>(null); const menuRight = useRef<Menu>(null);
const handleAddCharacter = useCallback(() => { const handleAddCharacter = useCallback(() => {
@@ -24,34 +28,40 @@ export const MapContextMenu = ({ onShowOnTheMap, onShowMapSettings }: MapContext
}, [outCommand]); }, [outCommand]);
const items = useMemo(() => { const items = useMemo(() => {
return [ return (
{ [
label: 'Tracking', {
icon: 'pi pi-user-plus', label: 'Tracking',
command: handleAddCharacter, icon: 'pi pi-user-plus',
}, command: handleAddCharacter,
{ visible: true,
label: 'On the map', },
icon: 'pi pi-hashtag', {
command: onShowOnTheMap, label: 'On the map',
}, icon: 'pi pi-hashtag',
{ separator: true }, command: onShowOnTheMap,
{ visible: canTrackCharacters,
label: 'Settings', },
icon: `pi pi-cog`, { separator: true, visible: true },
command: onShowMapSettings, {
}, label: 'Settings',
{ icon: `pi pi-cog`,
label: 'Dock menu', command: onShowMapSettings,
icon: 'pi pi-window-maximize', visible: true,
command: () => },
setInterfaceSettings(x => ({ {
...x, label: 'Dock menu',
isShowMenu: !x.isShowMenu, icon: 'pi pi-window-maximize',
})), command: () =>
}, setInterfaceSettings(x => ({
] as MenuItem[]; ...x,
}, [handleAddCharacter, onShowMapSettings, onShowOnTheMap, setInterfaceSettings]); isShowMenu: !x.isShowMenu,
})),
visible: true,
},
] as MenuItem[]
).filter(item => item.visible);
}, [canTrackCharacters, handleAddCharacter, onShowMapSettings, onShowOnTheMap, setInterfaceSettings]);
return ( return (
<div className="ml-1"> <div className="ml-1">

View File

@@ -53,6 +53,7 @@ const SYSTEMS_CHECKBOXES_PROPS: CheckboxesList = [
const SIGNATURES_CHECKBOXES_PROPS: CheckboxesList = [ const SIGNATURES_CHECKBOXES_PROPS: CheckboxesList = [
{ prop: UserSettingsRemoteProps.link_signature_on_splash, label: 'Link signature on splash' }, { prop: UserSettingsRemoteProps.link_signature_on_splash, label: 'Link signature on splash' },
{ prop: InterfaceStoredSettingsProps.isShowUnsplashedSignatures, label: 'Show unsplashed signatures' },
]; ];
const CONNECTIONS_CHECKBOXES_PROPS: CheckboxesList = [ const CONNECTIONS_CHECKBOXES_PROPS: CheckboxesList = [
@@ -61,6 +62,9 @@ const CONNECTIONS_CHECKBOXES_PROPS: CheckboxesList = [
const UI_CHECKBOXES_PROPS: CheckboxesList = [ const UI_CHECKBOXES_PROPS: CheckboxesList = [
{ prop: InterfaceStoredSettingsProps.isShowMenu, label: 'Enable compact map menu bar' }, { prop: InterfaceStoredSettingsProps.isShowMenu, label: 'Enable compact map menu bar' },
{ prop: InterfaceStoredSettingsProps.isThickConnections, label: 'Thicker connections' },
{ prop: InterfaceStoredSettingsProps.isShowBackgroundPattern, label: 'Show background pattern' },
{ prop: InterfaceStoredSettingsProps.isSoftBackground, label: 'Enable soft background' },
]; ];
export const MapSettings = ({ show, onHide }: MapSettingsProps) => { export const MapSettings = ({ show, onHide }: MapSettingsProps) => {
@@ -127,7 +131,7 @@ export const MapSettings = ({ show, onHide }: MapSettingsProps) => {
return ( return (
<Dialog <Dialog
header="Map settings" header="Map user settings"
visible={show} visible={show}
draggable={false} draggable={false}
style={{ width: '550px' }} style={{ width: '550px' }}

View File

@@ -8,6 +8,8 @@ import clsx from 'clsx';
import { CharacterTypeRaw, WithIsOwnCharacter } from '@/hooks/Mapper/types'; import { CharacterTypeRaw, WithIsOwnCharacter } from '@/hooks/Mapper/types';
import { CharacterCard, WdCheckbox } from '@/hooks/Mapper/components/ui-kit'; import { CharacterCard, WdCheckbox } from '@/hooks/Mapper/components/ui-kit';
import useLocalStorageState from 'use-local-storage-state'; import useLocalStorageState from 'use-local-storage-state';
import { useMapCheckPermissions, useMapGetOption } from '@/hooks/Mapper/mapRootProvider/hooks/api';
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
type WindowLocalSettingsType = { type WindowLocalSettingsType = {
compact: boolean; compact: boolean;
@@ -50,14 +52,22 @@ export const OnTheMap = ({ show, onHide }: OnTheMapProps) => {
defaultValue: STORED_DEFAULT_VALUES, defaultValue: STORED_DEFAULT_VALUES,
}); });
const restrictOfflineShowing = useMapGetOption('restrict_offline_showing');
const isAdminOrManager = useMapCheckPermissions([UserPermission.MANAGE_MAP]);
const showOffline = useMemo(
() => !restrictOfflineShowing || isAdminOrManager,
[isAdminOrManager, restrictOfflineShowing],
);
const sorted = useMemo(() => { const sorted = useMemo(() => {
const out = characters.map(x => ({ ...x, isOwn: userCharacters.includes(x.eve_id) })).sort(sortCharacters); const out = characters.map(x => ({ ...x, isOwn: userCharacters.includes(x.eve_id) })).sort(sortCharacters);
if (!settings.hideOffline) { if (showOffline && !settings.hideOffline) {
return out; return out;
} }
return out.filter(x => x.online); return out.filter(x => x.online);
}, [characters, settings.hideOffline, userCharacters]); }, [showOffline, characters, settings.hideOffline, userCharacters]);
return ( return (
<Sidebar <Sidebar
@@ -70,14 +80,16 @@ export const OnTheMap = ({ show, onHide }: OnTheMapProps) => {
> >
<div className={clsx(classes.SidebarContent, '')}> <div className={clsx(classes.SidebarContent, '')}>
<div className={'flex justify-end items-center gap-2 px-3'}> <div className={'flex justify-end items-center gap-2 px-3'}>
<WdCheckbox {showOffline && (
size="m" <WdCheckbox
labelSide="left" size="m"
label={'Hide offline'} labelSide="left"
value={settings.hideOffline} label={'Hide offline'}
classNameLabel="text-stone-400 hover:text-stone-200 transition duration-300" value={settings.hideOffline}
onChange={() => setSettings(() => ({ ...settings, hideOffline: !settings.hideOffline }))} classNameLabel="text-stone-400 hover:text-stone-200 transition duration-300"
/> onChange={() => setSettings(() => ({ ...settings, hideOffline: !settings.hideOffline }))}
/>
)}
</div> </div>
<VirtualScroller <VirtualScroller

View File

@@ -6,6 +6,9 @@ import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper'; import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit'; import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit';
import { useMapCheckPermissions } from '@/hooks/Mapper/mapRootProvider/hooks/api';
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
interface RightBarProps { interface RightBarProps {
onShowOnTheMap?: () => void; onShowOnTheMap?: () => void;
onShowMapSettings?: () => void; onShowMapSettings?: () => void;
@@ -14,6 +17,8 @@ interface RightBarProps {
export const RightBar = ({ onShowOnTheMap, onShowMapSettings }: RightBarProps) => { export const RightBar = ({ onShowOnTheMap, onShowMapSettings }: RightBarProps) => {
const { outCommand, interfaceSettings, setInterfaceSettings } = useMapRootState(); const { outCommand, interfaceSettings, setInterfaceSettings } = useMapRootState();
const canTrackCharacters = useMapCheckPermissions([UserPermission.TRACK_CHARACTER]);
const isShowMinimap = interfaceSettings.isShowMinimap === undefined ? true : interfaceSettings.isShowMinimap; const isShowMinimap = interfaceSettings.isShowMinimap === undefined ? true : interfaceSettings.isShowMinimap;
const handleAddCharacter = useCallback(() => { const handleAddCharacter = useCallback(() => {
@@ -64,19 +69,21 @@ export const RightBar = ({ onShowOnTheMap, onShowMapSettings }: RightBarProps) =
</button> </button>
</WdTooltipWrapper> </WdTooltipWrapper>
<WdTooltipWrapper content="Show on the map" position={TooltipPosition.left}> {canTrackCharacters && (
<button <WdTooltipWrapper content="Show on the map" position={TooltipPosition.left}>
className="btn bg-transparent text-gray-400 hover:text-white border-transparent hover:bg-transparent py-2 h-auto min-h-auto" <button
type="button" className="btn bg-transparent text-gray-400 hover:text-white border-transparent hover:bg-transparent py-2 h-auto min-h-auto"
onClick={onShowOnTheMap} type="button"
> onClick={onShowOnTheMap}
<i className="pi pi-hashtag"></i> >
</button> <i className="pi pi-hashtag"></i>
</WdTooltipWrapper> </button>
</WdTooltipWrapper>
)}
</div> </div>
<div className="flex flex-col items-center mb-2 gap-1"> <div className="flex flex-col items-center mb-2 gap-1">
<WdTooltipWrapper content="User settings" position={TooltipPosition.left}> <WdTooltipWrapper content="Map user settings" position={TooltipPosition.left}>
<button <button
className="btn bg-transparent text-gray-400 hover:text-white border-transparent hover:bg-transparent py-2 h-auto min-h-auto" className="btn bg-transparent text-gray-400 hover:text-white border-transparent hover:bg-transparent py-2 h-auto min-h-auto"
type="button" type="button"

View File

@@ -1,6 +1,5 @@
import { Dialog } from 'primereact/dialog'; import { Dialog } from 'primereact/dialog';
import { useCallback, useEffect } from 'react'; import { useCallback, useEffect } from 'react';
// import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { OutCommand, SignatureGroup, SystemSignature } from '@/hooks/Mapper/types'; import { OutCommand, SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
import { Controller, FormProvider, useForm } from 'react-hook-form'; import { Controller, FormProvider, useForm } from 'react-hook-form';
import { import {
@@ -25,102 +24,120 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
const { outCommand } = useMapRootState(); const { outCommand } = useMapRootState();
const handleShow = async () => {}; const handleShow = async () => {};
const form = useForm<Partial<SystemSignaturePrepared>>({}); const signatureForm = useForm<Partial<SystemSignaturePrepared>>({});
const handleSave = useCallback(async () => { const handleSave = useCallback(
if (!signatureData) { async (e: any) => {
return; e?.preventDefault();
} if (!signatureData) {
return;
}
const { group, ...values } = form.getValues(); const { group, ...values } = signatureForm.getValues();
let out = { ...signatureData }; let out = { ...signatureData };
switch (group) { switch (group) {
case SignatureGroup.Wormhole: case SignatureGroup.Wormhole:
if (values.linked_system) { if (values.linked_system) {
await outCommand({ await outCommand({
type: OutCommand.linkSignatureToSystem, type: OutCommand.linkSignatureToSystem,
data: { data: {
signature_eve_id: signatureData.eve_id, signature_eve_id: signatureData.eve_id,
solar_system_source: systemId, solar_system_source: systemId,
solar_system_target: values.linked_system, solar_system_target: values.linked_system,
}, },
}); });
} }
if (values.type != null) { out = {
out = { ...out, type: values.type }; ...out,
} custom_info: JSON.stringify({
k162Type: values.k162Type,
}),
};
if (signatureData.group !== SignatureGroup.Wormhole) { if (values.type != null) {
out = { ...out, name: '' }; out = { ...out, type: values.type };
} }
break; if (signatureData.group !== SignatureGroup.Wormhole) {
case SignatureGroup.CosmicSignature: out = { ...out, name: '' };
out = { ...out, type: '', name: '' }; }
break;
default:
if (values.name != null) {
out = { ...out, name: values.name ?? '' };
}
}
if (values.description != null) { break;
out = { ...out, description: values.description }; 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! };
// 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({ await outCommand({
type: OutCommand.unlinkSignature, type: OutCommand.updateSignatures,
data: { signature_eve_id: signatureData.eve_id, solar_system_source: systemId }, data: {
system_id: systemId,
added: [],
updated: [out],
removed: [],
},
}); });
out = { ...out, type: '' }; signatureForm.reset();
} onHide();
},
if (group === SignatureGroup.Wormhole && signatureData.linked_system != null && values.linked_system === null) { [signatureForm, onHide, outCommand, signatureData, systemId],
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(() => { useEffect(() => {
if (!signatureData) { if (!signatureData) {
form.reset(); signatureForm.reset();
return; return;
} }
const { linked_system, ...rest } = signatureData; const { linked_system, custom_info, ...rest } = signatureData;
form.reset({ let k162Type = null;
if (custom_info) {
const customInfo = JSON.parse(custom_info);
k162Type = customInfo.k162Type;
}
signatureForm.reset({
linked_system: linked_system?.solar_system_id.toString() ?? undefined, linked_system: linked_system?.solar_system_id.toString() ?? undefined,
k162Type: k162Type,
...rest, ...rest,
}); });
}, [form, signatureData]); }, [signatureForm, signatureData]);
return ( return (
<Dialog <Dialog
@@ -138,32 +155,34 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map
}} }}
> >
<SystemsSettingsProvider initialValue={{ systemId }}> <SystemsSettingsProvider initialValue={{ systemId }}>
<FormProvider {...form}> <FormProvider {...signatureForm}>
<div className="flex flex-col gap-2 justify-between"> <form onSubmit={handleSave}>
<div className="w-full flex flex-col gap-1 p-1 min-h-[150px]"> <div className="flex flex-col gap-2 justify-between">
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]"> <div className="w-full flex flex-col gap-1 p-1 min-h-[150px]">
<span>Group:</span> <label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
<SignatureGroupSelect name="group" /> <span>Group:</span>
</label> <SignatureGroupSelect name="group" />
</label>
<SignatureGroupContent /> <SignatureGroupContent />
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]"> <label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
<span>Description:</span> <span>Description:</span>
<Controller <Controller
name="description" name="description"
control={form.control} control={signatureForm.control}
render={({ field }) => ( render={({ field }) => (
<InputText placeholder="Type description" value={field.value} onChange={field.onChange} /> <InputText placeholder="Type description" value={field.value} onChange={field.onChange} />
)} )}
/> />
</label> </label>
</div>
<div className="flex gap-2 justify-end">
<Button onClick={handleSave} outlined size="small" label="Save"></Button>
</div>
</div> </div>
</form>
<div className="flex gap-2 justify-end">
<Button onClick={handleSave} outlined size="small" label="Save"></Button>
</div>
</div>
</FormProvider> </FormProvider>
</SystemsSettingsProvider> </SystemsSettingsProvider>
</Dialog> </Dialog>

View File

@@ -1,7 +1,13 @@
import { useFormContext } from 'react-hook-form';
import { SystemSignature } from '@/hooks/Mapper/types';
import { SignatureWormholeTypeSelect } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureWormholeTypeSelect'; import { SignatureWormholeTypeSelect } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureWormholeTypeSelect';
import { SignatureK162TypeSelect } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureK162TypeSelect';
import { SignatureLeadsToSelect } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureLeadsToSelect'; import { SignatureLeadsToSelect } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureLeadsToSelect';
export const SignatureGroupContentWormholes = () => { export const SignatureGroupContentWormholes = () => {
const { watch } = useFormContext<SystemSignature>();
const type = watch('type');
return ( return (
<> <>
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]"> <label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
@@ -9,6 +15,13 @@ export const SignatureGroupContentWormholes = () => {
<SignatureWormholeTypeSelect name="type" /> <SignatureWormholeTypeSelect name="type" />
</label> </label>
{type === 'K162' && (
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
<span>K162 Type:</span>
<SignatureK162TypeSelect name="k162Type" />
</label>
)}
<label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]"> <label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]">
<span>Leads To:</span> <span>Leads To:</span>
<SignatureLeadsToSelect name="linked_system" /> <SignatureLeadsToSelect name="linked_system" />

View File

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

View File

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

View File

@@ -13,13 +13,14 @@ import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
// @ts-ignore // @ts-ignore
const renderLinkedSystemItem = (option: { value: string }) => { const renderLinkedSystemItem = (option: { value: string }) => {
if (option.value == null) { const { value } = option;
return <div className="flex gap-2 items-center">No linked system</div>; if (value == null) {
return <div className="flex gap-2 items-center">- Unknown -</div>;
} }
return ( return (
<div className="flex gap-2 items-center"> <div className="flex gap-2 items-center">
<SystemView systemId={option.value} className={classes.SystemView} /> <SystemView systemId={value} className={classes.SystemView} />
</div> </div>
); );
}; };
@@ -65,6 +66,7 @@ export const SignatureLeadsToSelect = ({ name, defaultValue = '' }: SignatureLea
const leadsToOptions = useMemo(() => { const leadsToOptions = useMemo(() => {
return [ return [
{ value: null }, { value: null },
...leadsTo ...leadsTo
.filter(systemId => { .filter(systemId => {
const systemStatic = systemStatics.get(parseInt(systemId)); const systemStatic = systemStatics.get(parseInt(systemId));

View File

@@ -17,7 +17,25 @@ const getPossibleWormholes = (systemStatic: SolarSystemStaticInfoRaw, wormholes:
// @ts-ignore // @ts-ignore
const spawnClassGroup = SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS[whType]; const spawnClassGroup = SOLAR_SYSTEM_CLASSES_TO_CLASS_GROUPS[whType];
const possibleWHTypes = wormholes.filter(x => x.src.includes(spawnClassGroup)); const possibleWHTypes = wormholes.filter(x => {
return x.src.some(x => {
const [group, type] = x.split('-');
// eslint-disable-next-line no-console
console.log('JOipP', `group, type`, group, type);
if (type === 'shattered') {
return systemStatic.is_shattered && group === spawnClassGroup;
}
return group === spawnClassGroup;
});
});
// eslint-disable-next-line no-console
console.log('JOipP', `possibleWHTypes`, possibleWHTypes);
// debugger;
return { return {
statics: possibleWHTypes statics: possibleWHTypes

View File

@@ -1,2 +1,3 @@
export * from './SignatureGroupSelect'; export * from './SignatureGroupSelect';
export * from './SignatureGroupContent'; export * from './SignatureGroupContent';
export * from './SignatureK162TypeSelect';

View File

@@ -1,14 +1,14 @@
import { Map } from '@/hooks/Mapper/components/map/Map.tsx'; import { Map } from '@/hooks/Mapper/components/map/Map.tsx';
import { ForwardedRef, useCallback, useRef, useState } from 'react'; import { useCallback, useRef, useState } from 'react';
import { MapHandlers, OutCommand, OutCommandHandler, SolarSystemConnection } from '@/hooks/Mapper/types'; import { OutCommand, OutCommandHandler, SolarSystemConnection } from '@/hooks/Mapper/types';
import { MapRootData, useMapRootState } from '@/hooks/Mapper/mapRootProvider'; import { MapRootData, useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts'; import { OnMapSelectionChange } from '@/hooks/Mapper/components/map/map.types.ts';
import isEqual from 'lodash.isequal'; import isEqual from 'lodash.isequal';
import { ContextMenuSystem, useContextMenuSystemHandlers } from '@/hooks/Mapper/components/contexts'; import { ContextMenuSystem, useContextMenuSystemHandlers } from '@/hooks/Mapper/components/contexts';
import { import {
SystemCustomLabelDialog, SystemCustomLabelDialog,
SystemSettingsDialog,
SystemLinkSignatureDialog, SystemLinkSignatureDialog,
SystemSettingsDialog,
} from '@/hooks/Mapper/components/mapInterface/components'; } from '@/hooks/Mapper/components/mapInterface/components';
import classes from './MapWrapper.module.scss'; import classes from './MapWrapper.module.scss';
import { Connections } from '@/hooks/Mapper/components/mapRootContent/components/Connections'; import { Connections } from '@/hooks/Mapper/components/mapRootContent/components/Connections';
@@ -20,25 +20,47 @@ import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts';
import { useMapEventListener } from '@/hooks/Mapper/events'; import { useMapEventListener } from '@/hooks/Mapper/events';
import { STORED_INTERFACE_DEFAULT_VALUES } from '@/hooks/Mapper/mapRootProvider/MapRootProvider'; import { STORED_INTERFACE_DEFAULT_VALUES } from '@/hooks/Mapper/mapRootProvider/MapRootProvider';
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
interface MapWrapperProps { import { useCommonMapEventProcessor } from '@/hooks/Mapper/components/mapWrapper/hooks/useCommonMapEventProcessor.ts';
refn: ForwardedRef<MapHandlers>;
}
// TODO: INFO - this component needs for abstract work with Map instance // TODO: INFO - this component needs for abstract work with Map instance
export const MapWrapper = ({ refn }: MapWrapperProps) => { export const MapWrapper = () => {
const { const {
update, update,
outCommand, outCommand,
data: { selectedConnections, selectedSystems, hubs, systems }, data: { selectedConnections, selectedSystems, hubs, systems },
interfaceSettings: { isShowMenu, isShowMinimap = STORED_INTERFACE_DEFAULT_VALUES.isShowMinimap, isShowKSpace }, interfaceSettings: {
isShowMenu,
isShowMinimap = STORED_INTERFACE_DEFAULT_VALUES.isShowMinimap,
isShowKSpace,
isThickConnections,
isShowBackgroundPattern,
isSoftBackground,
},
} = useMapRootState(); } = useMapRootState();
const { deleteSystems } = useDeleteSystems();
const { mapRef, runCommand } = useCommonMapEventProcessor();
const { open, ...systemContextProps } = useContextMenuSystemHandlers({ systems, hubs, outCommand }); const { open, ...systemContextProps } = useContextMenuSystemHandlers({ systems, hubs, outCommand });
const { handleSystemMultipleContext, ...systemMultipleCtxProps } = useContextMenuSystemMultipleHandlers(); const { handleSystemMultipleContext, ...systemMultipleCtxProps } = useContextMenuSystemMultipleHandlers();
const ref = useRef({ selectedConnections, selectedSystems, systemContextProps, systems }); const [openSettings, setOpenSettings] = useState<string | null>(null);
ref.current = { selectedConnections, selectedSystems, systemContextProps, systems }; 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( const onSelectionChange: OnMapSelectionChange = useCallback(
({ systems, connections }) => { ({ systems, connections }) => {
@@ -59,9 +81,6 @@ export const MapWrapper = ({ refn }: MapWrapperProps) => {
[update], [update],
); );
const [openSettings, setOpenSettings] = useState<string | null>(null);
const [openLinkSignatures, setOpenLinkSignatures] = useState<any | null>(null);
const [openCustomLabel, setOpenCustomLabel] = useState<string | null>(null);
const handleCommand: OutCommandHandler = useCallback( const handleCommand: OutCommandHandler = useCallback(
event => { event => {
switch (event.type) { switch (event.type) {
@@ -95,22 +114,19 @@ export const MapWrapper = ({ refn }: MapWrapperProps) => {
[open], [open],
); );
const [selectedConnection, setSelectedConnection] = useState<SolarSystemConnection | null>(null);
const handleConnectionDbClick = useCallback((e: SolarSystemConnection) => setSelectedConnection(e), []); const handleConnectionDbClick = useCallback((e: SolarSystemConnection) => setSelectedConnection(e), []);
useMapEventListener(event => { const handleManualDelete = useCallback((toDelete: string[]) => {
switch (event.name) { const restDel = toDelete.filter(x => ref.current.systems.some(y => y.id === x));
case Commands.linkSignatureToSystem: if (restDel.length > 0) {
setOpenLinkSignatures(event.data); ref.current.deleteSystems(restDel);
return true;
} }
}); }, []);
return ( return (
<> <>
<Map <Map
ref={refn} ref={mapRef}
onCommand={handleCommand} onCommand={handleCommand}
onSelectionChange={onSelectionChange} onSelectionChange={onSelectionChange}
onConnectionInfoClick={handleConnectionDbClick} onConnectionInfoClick={handleConnectionDbClick}
@@ -119,6 +135,10 @@ export const MapWrapper = ({ refn }: MapWrapperProps) => {
minimapClasses={!isShowMenu ? classes.MiniMap : undefined} minimapClasses={!isShowMenu ? classes.MiniMap : undefined}
isShowMinimap={isShowMinimap} isShowMinimap={isShowMinimap}
showKSpaceBG={isShowKSpace} showKSpaceBG={isShowKSpace}
onManualDelete={handleManualDelete}
isThickConnections={isThickConnections}
isShowBackgroundPattern={isShowBackgroundPattern}
isSoftBackground={isSoftBackground}
/> />
{openSettings != null && ( {openSettings != null && (

View File

@@ -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,
};
};

View File

@@ -18,7 +18,7 @@ const Topbar = ({ children }: WithChildren) => {
<nav <nav
className={clsx( className={clsx(
'px-2 flex items-center justify-center min-w-0 h-12 pointer-events-auto', 'px-2 flex items-center justify-center min-w-0 h-12 pointer-events-auto',
'border-b border-gray-900 bg-gray-800 bg-opacity-5', 'border-b border-stone-800 bg-gray-800 bg-opacity-5',
'bg-opacity-70 bg-neutral-900', 'bg-opacity-70 bg-neutral-900',
)} )}
> >

View File

@@ -4,7 +4,7 @@ import classes from './CharacterCard.module.scss';
import { SystemView } from '@/hooks/Mapper/components/ui-kit/SystemView'; import { SystemView } from '@/hooks/Mapper/components/ui-kit/SystemView';
import { CharacterTypeRaw, WithIsOwnCharacter } from '@/hooks/Mapper/types'; import { CharacterTypeRaw, WithIsOwnCharacter } from '@/hooks/Mapper/types';
import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts'; import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; import { emitMapEvent } from '@/hooks/Mapper/events';
type CharacterCardProps = { type CharacterCardProps = {
compact?: boolean; compact?: boolean;
@@ -34,11 +34,12 @@ export const CharacterCard = ({
useSystemsCache, useSystemsCache,
...char ...char
}: CharacterCardProps) => { }: CharacterCardProps) => {
const { mapRef } = useMapRootState();
const handleSelect = useCallback(() => { const handleSelect = useCallback(() => {
mapRef.current?.command(Commands.centerSystem, char?.location?.solar_system_id?.toString()); emitMapEvent({
}, [mapRef, char]); name: Commands.centerSystem,
data: char?.location?.solar_system_id?.toString(),
});
}, [char]);
return ( return (
<div className={clsx(classes.CharacterCard, 'w-full text-xs', 'flex flex-col box-border')} onClick={handleSelect}> <div className={clsx(classes.CharacterCard, 'w-full text-xs', 'flex flex-col box-border')} onClick={handleSelect}>

View File

@@ -3,15 +3,23 @@ import { SystemViewStandalone } from '@/hooks/Mapper/components/ui-kit';
import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts'; import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { SolarSystemStaticInfoRaw } from '@/hooks/Mapper/types';
export type SystemViewProps = { export type SystemViewProps = {
systemId: string; systemId: string;
systemInfo?: SolarSystemStaticInfoRaw;
hideRegion?: boolean; hideRegion?: boolean;
useSystemsCache?: boolean; useSystemsCache?: boolean;
showCustomName?: boolean; showCustomName?: boolean;
} & WithClassName; } & WithClassName;
export const SystemView = ({ systemId, hideRegion, className, showCustomName }: SystemViewProps) => { export const SystemView = ({
systemId,
systemInfo: customSystemInfo,
hideRegion,
className,
showCustomName,
}: SystemViewProps) => {
const memSystems = useMemo(() => [systemId], [systemId]); const memSystems = useMemo(() => [systemId], [systemId]);
const { systems, loading } = useLoadSystemStatic({ systems: memSystems }); const { systems, loading } = useLoadSystemStatic({ systems: memSystems });
@@ -20,9 +28,12 @@ export const SystemView = ({ systemId, hideRegion, className, showCustomName }:
} = useMapRootState(); } = useMapRootState();
const systemInfo = useMemo(() => { const systemInfo = useMemo(() => {
if (!systemId) {
return customSystemInfo;
}
return systems.get(parseInt(systemId)); return systems.get(parseInt(systemId));
// eslint-disable-next-line // eslint-disable-next-line
}, [systemId, systems, loading]); }, [customSystemInfo, systemId, systems, loading]);
const mapSystemInfo = useMemo(() => { const mapSystemInfo = useMemo(() => {
if (!showCustomName) { if (!showCustomName) {

View File

@@ -25,7 +25,6 @@ export const SystemViewStandalone = ({
className, className,
hideRegion, hideRegion,
customName, customName,
class_title, class_title,
system_class, system_class,
solar_system_name, solar_system_name,
@@ -38,6 +37,7 @@ export const SystemViewStandalone = ({
...props ...props
}: SystemViewStandaloneProps) => { }: SystemViewStandaloneProps) => {
const classTitleColor = getSystemClassStyles({ systemClass: system_class, security }); const classTitleColor = getSystemClassStyles({ systemClass: system_class, security });
const isWH = isWormholeSpace(system_class); const isWH = isWormholeSpace(system_class);
const handleClick = useCallback( const handleClick = useCallback(

View File

@@ -1,5 +1,4 @@
.WHClassViewRoot { .WHClassViewRoot {
} }
.WHClassViewContent { .WHClassViewContent {

View File

@@ -18,7 +18,9 @@ export interface WHClassViewProps {
whClassName: string; whClassName: string;
noOffset?: boolean; noOffset?: boolean;
useShortTitle?: boolean; useShortTitle?: boolean;
hideTooltip?: boolean;
hideWhClass?: boolean; hideWhClass?: boolean;
hideWhClassName?: boolean;
highlightName?: boolean; highlightName?: boolean;
className?: string; className?: string;
classNameWh?: string; classNameWh?: string;
@@ -28,7 +30,9 @@ export const WHClassView = ({
whClassName, whClassName,
noOffset, noOffset,
useShortTitle, useShortTitle,
hideTooltip,
hideWhClass, hideWhClass,
hideWhClassName,
highlightName, highlightName,
className, className,
classNameWh, classNameWh,
@@ -45,25 +49,27 @@ export const WHClassView = ({
return ( return (
<div className={clsx(classes.WHClassViewRoot, className)}> <div className={clsx(classes.WHClassViewRoot, className)}>
<Tooltip {!hideTooltip && (
target={`.wh-name${whClassName}${uid}`} <Tooltip
position="right" target={`.wh-name${whClassName}${uid}`}
mouseTrack position="right"
mouseTrackLeft={20} mouseTrack
mouseTrackTop={30} mouseTrackLeft={20}
className="border border-green-300 rounded border-opacity-10 bg-stone-900 bg-opacity-90 " mouseTrackTop={30}
> 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"> <div className="flex gap-3">
<InfoDrawer title="Total mass">{prepareMass(whData.total_mass)}</InfoDrawer> <div className="flex flex-col gap-1">
<InfoDrawer title="Jump mass">{prepareMass(whData.max_mass_per_jump)}</InfoDrawer> <InfoDrawer title="Total mass">{prepareMass(whData.total_mass)}</InfoDrawer>
<InfoDrawer title="Jump mass">{prepareMass(whData.max_mass_per_jump)}</InfoDrawer>
</div>
<div className="flex flex-col gap-1">
<InfoDrawer title="Lifetime">{whData.lifetime}h</InfoDrawer>
<InfoDrawer title="Mass regen">{prepareMass(whData.mass_regen)}</InfoDrawer>
</div>
</div> </div>
<div className="flex flex-col gap-1"> </Tooltip>
<InfoDrawer title="Lifetime">{whData.lifetime}h</InfoDrawer> )}
<InfoDrawer title="Mass regen">{prepareMass(whData.mass_regen)}</InfoDrawer>
</div>
</div>
</Tooltip>
<div <div
className={clsx( className={clsx(
@@ -73,7 +79,7 @@ export const WHClassView = ({
`wh-name${whClassName}${uid}`, `wh-name${whClassName}${uid}`,
)} )}
> >
<span className={clsx({ [whClassStyle]: highlightName })}>{whClassName}</span> {!hideWhClassName && <span className={clsx({ [whClassStyle]: highlightName })}>{whClassName}</span>}
{!hideWhClass && whClass && ( {!hideWhClass && whClass && (
<span className={clsx(classes.WHClassName, whClassStyle, classNameWh)}> <span className={clsx(classes.WHClassName, whClassStyle, classNameWh)}>
{useShortTitle ? whClass.shortTitle : whClass.shortName} {useShortTitle ? whClass.shortTitle : whClass.shortName}

View File

@@ -1,13 +1,13 @@
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
export const useClipboard = () => { export const useClipboard = () => {
const [clipboardContent, setClipboardContent] = useState<string | null>(null); const [clipboardContent, setClipboardContent] = useState<{ text: string } | null>(null);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const getClipboardContent = useCallback(async () => { const getClipboardContent = useCallback(async () => {
try { try {
const text = await navigator.clipboard.readText(); const text = await navigator.clipboard.readText();
setClipboardContent(text); setClipboardContent({ text });
setError(null); setError(null);
} catch (err) { } catch (err) {
setError('Failed to read clipboard content.'); setError('Failed to read clipboard content.');
@@ -18,7 +18,7 @@ export const useClipboard = () => {
const handlePaste = (event: ClipboardEvent) => { const handlePaste = (event: ClipboardEvent) => {
const text = event.clipboardData?.getData('text'); const text = event.clipboardData?.getData('text');
if (text) { if (text) {
setClipboardContent(text); setClipboardContent({ text });
setError(null); setError(null);
} }
}; };
@@ -30,5 +30,5 @@ export const useClipboard = () => {
}; };
}, []); }, []);
return { clipboardContent, error, getClipboardContent }; return { clipboardContent, error, getClipboardContent, setClipboardContent };
}; };

View File

@@ -1,6 +1,6 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
export const useHotkey = (isMetaKey: boolean, hotkeys: string[], callback: () => void) => { export const useHotkey = (isMetaKey: boolean, hotkeys: string[], callback: (e: KeyboardEvent) => void) => {
useEffect(() => { useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => { const handleKeyDown = (event: KeyboardEvent) => {
if ((!isMetaKey || event.ctrlKey || event.metaKey) && hotkeys.includes(event.key)) { if ((!isMetaKey || event.ctrlKey || event.metaKey) && hotkeys.includes(event.key)) {
@@ -8,14 +8,14 @@ export const useHotkey = (isMetaKey: boolean, hotkeys: string[], callback: () =>
return; return;
} }
event.preventDefault(); event.preventDefault();
callback(); callback(event);
} }
}; };
window.addEventListener('keydown', handleKeyDown); window.addEventListener('keydown', handleKeyDown, { capture: true });
return () => { return () => {
window.removeEventListener('keydown', handleKeyDown); window.removeEventListener('keydown', handleKeyDown, { capture: true });
}; };
}, [isMetaKey, hotkeys, callback]); }, [isMetaKey, hotkeys, callback]);
}; };

View File

@@ -1,6 +1,6 @@
import { ContextStoreDataUpdate, useContextStore } from '@/hooks/Mapper/utils'; import { ContextStoreDataUpdate, useContextStore } from '@/hooks/Mapper/utils';
import { createContext, Dispatch, ForwardedRef, forwardRef, RefObject, SetStateAction, useContext } from 'react'; import { createContext, Dispatch, ForwardedRef, forwardRef, SetStateAction, useContext } from 'react';
import { MapHandlers, MapUnionTypes, OutCommandHandler, SolarSystemConnection } from '@/hooks/Mapper/types'; import { MapUnionTypes, OutCommandHandler, SolarSystemConnection } from '@/hooks/Mapper/types';
import { useMapRootHandlers } from '@/hooks/Mapper/mapRootProvider/hooks'; import { useMapRootHandlers } from '@/hooks/Mapper/mapRootProvider/hooks';
import { WithChildren } from '@/hooks/Mapper/types/common.ts'; import { WithChildren } from '@/hooks/Mapper/types/common.ts';
import useLocalStorageState from 'use-local-storage-state'; import useLocalStorageState from 'use-local-storage-state';
@@ -25,30 +25,43 @@ const INITIAL_DATA: MapRootData = {
selectedSystems: [], selectedSystems: [],
selectedConnections: [], selectedConnections: [],
userPermissions: {},
options: {},
}; };
export enum InterfaceStoredSettingsProps { export enum InterfaceStoredSettingsProps {
isShowMenu = 'isShowMenu', isShowMenu = 'isShowMenu',
isShowMinimap = 'isShowMinimap', isShowMinimap = 'isShowMinimap',
isShowKSpace = 'isShowKSpace', isShowKSpace = 'isShowKSpace',
isThickConnections = 'isThickConnections',
isShowUnsplashedSignatures = 'isShowUnsplashedSignatures',
isShowBackgroundPattern = 'isShowBackgroundPattern',
isSoftBackground = 'isSoftBackground',
} }
export type InterfaceStoredSettings = { export type InterfaceStoredSettings = {
isShowMenu: boolean; isShowMenu: boolean;
isShowMinimap: boolean; isShowMinimap: boolean;
isShowKSpace: boolean; isShowKSpace: boolean;
isThickConnections: boolean;
isShowUnsplashedSignatures: boolean;
isShowBackgroundPattern: boolean;
isSoftBackground: boolean;
}; };
export const STORED_INTERFACE_DEFAULT_VALUES: InterfaceStoredSettings = { export const STORED_INTERFACE_DEFAULT_VALUES: InterfaceStoredSettings = {
isShowMenu: false, isShowMenu: false,
isShowMinimap: true, isShowMinimap: true,
isShowKSpace: false, isShowKSpace: false,
isThickConnections: false,
isShowUnsplashedSignatures: false,
isShowBackgroundPattern: true,
isSoftBackground: false,
}; };
export interface MapRootContextProps { export interface MapRootContextProps {
update: ContextStoreDataUpdate<MapRootData>; update: ContextStoreDataUpdate<MapRootData>;
data: MapRootData; data: MapRootData;
mapRef: RefObject<MapHandlers>;
outCommand: OutCommandHandler; outCommand: OutCommandHandler;
interfaceSettings: InterfaceStoredSettings; interfaceSettings: InterfaceStoredSettings;
setInterfaceSettings: Dispatch<SetStateAction<InterfaceStoredSettings>>; setInterfaceSettings: Dispatch<SetStateAction<InterfaceStoredSettings>>;
@@ -57,7 +70,6 @@ export interface MapRootContextProps {
const MapRootContext = createContext<MapRootContextProps>({ const MapRootContext = createContext<MapRootContextProps>({
update: () => {}, update: () => {},
data: { ...INITIAL_DATA }, data: { ...INITIAL_DATA },
mapRef: { current: null },
// @ts-ignore // @ts-ignore
outCommand: async () => void 0, outCommand: async () => void 0,
interfaceSettings: STORED_INTERFACE_DEFAULT_VALUES, interfaceSettings: STORED_INTERFACE_DEFAULT_VALUES,
@@ -67,7 +79,6 @@ const MapRootContext = createContext<MapRootContextProps>({
type MapRootProviderProps = { type MapRootProviderProps = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
fwdRef: ForwardedRef<any>; fwdRef: ForwardedRef<any>;
mapRef: RefObject<MapHandlers>;
outCommand: OutCommandHandler; outCommand: OutCommandHandler;
} & WithChildren; } & WithChildren;
@@ -78,7 +89,7 @@ const MapRootHandlers = forwardRef(({ children }: WithChildren, fwdRef: Forwarde
}); });
// eslint-disable-next-line react/display-name // eslint-disable-next-line react/display-name
export const MapRootProvider = ({ children, fwdRef, mapRef, outCommand }: MapRootProviderProps) => { export const MapRootProvider = ({ children, fwdRef, outCommand }: MapRootProviderProps) => {
const { update, ref } = useContextStore<MapRootData>({ ...INITIAL_DATA }); const { update, ref } = useContextStore<MapRootData>({ ...INITIAL_DATA });
const [interfaceSettings, setInterfaceSettings] = useLocalStorageState<InterfaceStoredSettings>( const [interfaceSettings, setInterfaceSettings] = useLocalStorageState<InterfaceStoredSettings>(
@@ -94,7 +105,6 @@ export const MapRootProvider = ({ children, fwdRef, mapRef, outCommand }: MapRoo
update, update,
data: ref, data: ref,
outCommand: outCommand, outCommand: outCommand,
mapRef: mapRef,
setInterfaceSettings, setInterfaceSettings,
interfaceSettings, interfaceSettings,
}} }}

View File

@@ -1,5 +1,7 @@
export * from './useMapInit'; export * from './useMapInit';
export * from './useMapUpdated'; export * from './useMapUpdated';
export * from './useMapCheckPermissions';
export * from './useMapGetOption';
export * from './useRoutes'; export * from './useRoutes';
export * from './useCommandsConnections'; export * from './useCommandsConnections';
export * from './useCommandsSystems'; export * from './useCommandsSystems';

View File

@@ -1,47 +1,87 @@
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useCallback, useRef } from 'react'; import { useCallback, useRef } from 'react';
import { CommandAddSystems, CommandRemoveSystems, CommandUpdateSystems } from '@/hooks/Mapper/types'; import { CommandAddSystems, CommandRemoveSystems, CommandUpdateSystems } from '@/hooks/Mapper/types';
import { useLoadSystemStatic } from '@/hooks/Mapper/mapRootProvider/hooks/useLoadSystemStatic.ts';
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts';
import { emitMapEvent } from '@/hooks/Mapper/events';
import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts';
export const useCommandsSystems = () => { export const useCommandsSystems = () => {
const { const {
update, update,
data: { systems }, data: { systems },
outCommand,
} = useMapRootState(); } = useMapRootState();
const ref = useRef({ systems, update }); const { addSystemStatic } = useLoadSystemStatic({ systems: [] });
ref.current = { systems, update };
const addSystems = useCallback( const ref = useRef({ systems, update, addSystemStatic });
(addSystems: CommandAddSystems) => { ref.current = { systems, update, addSystemStatic };
update({
systems: [...ref.current.systems.filter(sys => addSystems.some(x => sys.id !== x.id)), ...addSystems], const addSystems = useCallback((systemsToAdd: CommandAddSystems) => {
}); const { update, addSystemStatic, systems } = ref.current;
},
[update], systemsToAdd.forEach(sys => {
); if (sys.system_static_info) {
addSystemStatic(sys.system_static_info);
}
});
update(
{
systems: [...systems.filter(sys => !systemsToAdd.some(x => sys.id === x.id)), ...systemsToAdd],
},
true,
);
}, []);
const removeSystems = useCallback((toRemove: CommandRemoveSystems) => { const removeSystems = useCallback((toRemove: CommandRemoveSystems) => {
const { update, systems } = ref.current; const { update, systems } = ref.current;
update({ update(
systems: systems.filter(x => !toRemove.includes(parseInt(x.id))), {
}); systems: systems.filter(x => !toRemove.includes(parseInt(x.id))),
},
true,
);
}, []); }, []);
const updateSystems = useCallback( const updateSystems = useCallback((updatedSystems: CommandUpdateSystems) => {
(systems: CommandUpdateSystems) => { const { update, systems } = ref.current;
const out = ref.current.systems.map(current => {
const newSystem = systems.find(x => current.id === x.id);
if (!newSystem) {
return current;
}
return newSystem; const out = systems.map(current => {
const newSystem = updatedSystems.find(x => current.id === x.id);
if (!newSystem) {
return current;
}
return newSystem;
});
update({ systems: out }, true);
}, []);
const updateSystemSignatures = useCallback(
async (systemId: string) => {
const { update, systems } = ref.current;
const { signatures } = await outCommand({
type: OutCommand.getSignatures,
data: { system_id: `${systemId}` },
}); });
update({ systems: out }); const out = systems.map(current => {
if (current.id === `${systemId}`) {
return { ...current, system_signatures: signatures };
}
return current;
});
update({ systems: out }, true);
emitMapEvent({ name: Commands.updateSystems, data: out });
}, },
[update], [outCommand],
); );
return { addSystems, removeSystems, updateSystems }; return { addSystems, removeSystems, updateSystems, updateSystemSignatures };
}; };

View File

@@ -0,0 +1,11 @@
import { useMemo } from 'react';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
export const useMapCheckPermissions = (permissions: UserPermission[]) => {
const {
data: { userPermissions },
} = useMapRootState();
return useMemo(() => permissions.every(x => userPermissions[x]), [permissions, userPermissions]);
};

View File

@@ -0,0 +1,10 @@
import { useMemo } from 'react';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
export const useMapGetOption = (option: string) => {
const {
data: { options },
} = useMapRootState();
return useMemo(() => options[option], [option, options]);
};

View File

@@ -19,6 +19,8 @@ export const useMapInit = () => {
user_characters, user_characters,
present_characters, present_characters,
hubs, hubs,
user_permissions,
options,
}: CommandInit) => { }: CommandInit) => {
const updateData: Partial<MapRootData> = {}; const updateData: Partial<MapRootData> = {};
@@ -51,10 +53,18 @@ export const useMapInit = () => {
updateData.connections = connections; updateData.connections = connections;
} }
if (user_permissions) {
updateData.userPermissions = user_permissions;
}
if (hubs) { if (hubs) {
updateData.hubs = hubs; updateData.hubs = hubs;
} }
if (options) {
updateData.options = options;
}
if (system_static_infos) { if (system_static_infos) {
system_static_infos.forEach(static_info => { system_static_infos.forEach(static_info => {
addSystemStatic(static_info); addSystemStatic(static_info);

View File

@@ -24,7 +24,7 @@ interface UseLoadSystemStaticProps {
systems: (number | string)[]; systems: (number | string)[];
} }
export const useLoadSystemStatic = ({ systems }: UseLoadSystemStaticProps) => { export const useLoadSystemStatic = ({ systems = [] }: UseLoadSystemStaticProps) => {
const { outCommand } = useMapRootState(); const { outCommand } = useMapRootState();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [lastUpdateKey, setLastUpdateKey] = useState(0); const [lastUpdateKey, setLastUpdateKey] = useState(0);
@@ -51,6 +51,9 @@ export const useLoadSystemStatic = ({ systems }: UseLoadSystemStaticProps) => {
}, []); }, []);
useEffect(() => { useEffect(() => {
if (!systems.length) {
return;
}
loadSystems(systems); loadSystems(systems);
// eslint-disable-next-line // eslint-disable-next-line
}, [systems]); }, [systems]);

View File

@@ -13,6 +13,7 @@ import {
CommandRemoveSystems, CommandRemoveSystems,
CommandRoutes, CommandRoutes,
Commands, Commands,
CommandSignaturesUpdated,
CommandUpdateConnection, CommandUpdateConnection,
CommandUpdateSystems, CommandUpdateSystems,
MapHandlers, MapHandlers,
@@ -31,7 +32,7 @@ import { emitMapEvent } from '@/hooks/Mapper/events';
export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => { export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
const mapInit = useMapInit(); const mapInit = useMapInit();
const { addSystems, removeSystems, updateSystems } = useCommandsSystems(); const { addSystems, removeSystems, updateSystems, updateSystemSignatures } = useCommandsSystems();
const { addConnections, removeConnections, updateConnection } = useCommandsConnections(); const { addConnections, removeConnections, updateConnection } = useCommandsConnections();
const { charactersUpdated, characterAdded, characterRemoved, characterUpdated, presentCharacters } = const { charactersUpdated, characterAdded, characterRemoved, characterUpdated, presentCharacters } =
useCommandsCharacters(); useCommandsCharacters();
@@ -44,87 +45,75 @@ export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => {
return { return {
command(type, data) { command(type, data) {
switch (type) { switch (type) {
case Commands.init: case Commands.init: // USED
mapInit(data as CommandInit); mapInit(data as CommandInit);
break; break;
case Commands.addSystems: case Commands.addSystems: // USED
addSystems(data as CommandAddSystems); addSystems(data as CommandAddSystems);
setTimeout(() => {
emitMapEvent({ name: Commands.addSystems, data });
}, 100);
break; break;
case Commands.updateSystems: case Commands.updateSystems: // USED
updateSystems(data as CommandUpdateSystems); updateSystems(data as CommandUpdateSystems);
break; break;
case Commands.removeSystems: case Commands.removeSystems: // USED
removeSystems(data as CommandRemoveSystems); removeSystems(data as CommandRemoveSystems);
setTimeout(() => {
emitMapEvent({ name: Commands.removeSystems, data });
}, 100);
break; break;
case Commands.addConnections: case Commands.addConnections: // USED
addConnections(data as CommandAddConnections); addConnections(data as CommandAddConnections);
setTimeout(() => {
emitMapEvent({ name: Commands.addConnections, data });
}, 100);
break; break;
case Commands.removeConnections: case Commands.removeConnections: // USED
removeConnections(data as CommandRemoveConnections); removeConnections(data as CommandRemoveConnections);
break; break;
case Commands.updateConnection: case Commands.updateConnection: // USED
updateConnection(data as CommandUpdateConnection); updateConnection(data as CommandUpdateConnection);
break; break;
case Commands.charactersUpdated: case Commands.charactersUpdated: // USED
charactersUpdated(data as CommandCharactersUpdated); charactersUpdated(data as CommandCharactersUpdated);
break; break;
case Commands.characterAdded: case Commands.characterAdded: // USED
characterAdded(data as CommandCharacterAdded); characterAdded(data as CommandCharacterAdded);
break; break;
case Commands.characterRemoved: case Commands.characterRemoved: // USED
characterRemoved(data as CommandCharacterRemoved); characterRemoved(data as CommandCharacterRemoved);
break; break;
case Commands.characterUpdated: case Commands.characterUpdated: // USED
characterUpdated(data as CommandCharacterUpdated); characterUpdated(data as CommandCharacterUpdated);
break; break;
case Commands.presentCharacters: case Commands.presentCharacters: // USED
presentCharacters(data as CommandPresentCharacters); presentCharacters(data as CommandPresentCharacters);
break; break;
case Commands.mapUpdated: case Commands.mapUpdated: // USED
mapUpdated(data as CommandMapUpdated); mapUpdated(data as CommandMapUpdated);
break; break;
case Commands.routes: case Commands.routes:
mapRoutes(data as CommandRoutes); mapRoutes(data as CommandRoutes);
break; break;
case Commands.centerSystem: case Commands.signaturesUpdated: // USED
updateSystemSignatures(data as CommandSignaturesUpdated);
break;
case Commands.linkSignatureToSystem: // USED
// do nothing here // do nothing here
break; break;
case Commands.selectSystem: case Commands.centerSystem: // USED
// do nothing here // do nothing here
break; break;
case Commands.linkSignatureToSystem: case Commands.selectSystem: // USED
// TODO command data type lost // do nothing here
// @ts-ignore
emitMapEvent({ name: Commands.linkSignatureToSystem, data });
break; break;
case Commands.killsUpdated: case Commands.killsUpdated:
// do nothing here // do nothing here
break; break;
case Commands.signaturesUpdated:
// TODO command data type lost
// @ts-ignore
emitMapEvent({ name: Commands.signaturesUpdated, data });
break;
default: default:
console.warn(`JOipP Interface handlers: Unknown command: ${type}`, data); console.warn(`JOipP Interface handlers: Unknown command: ${type}`, data);
break; break;
} }
emitMapEvent({ name: type, data });
}, },
}; };
}, },

View File

@@ -1,3 +1,8 @@
export enum ConnectionType {
wormhole,
gate,
}
export enum MassState { export enum MassState {
normal, normal,
half, half,
@@ -32,4 +37,6 @@ export type SolarSystemConnection = {
source: string; source: string;
target: string; target: string;
type?: ConnectionType;
}; };

View File

@@ -11,6 +11,10 @@ export type Passage = {
character: PassageLimitedCharacterType; character: PassageLimitedCharacterType;
}; };
export type ConnectionInfoOutput = {
marl_eol_time: string;
};
export type ConnectionOutput = { export type ConnectionOutput = {
passages: Passage[]; passages: Passage[];
}; };

View File

@@ -6,3 +6,4 @@ export * from './system';
export * from './mapUnionTypes'; export * from './mapUnionTypes';
export * from './signatures'; export * from './signatures';
export * from './connectionPassages'; export * from './connectionPassages';
export * from './permissions';

View File

@@ -4,6 +4,7 @@ import { WormholeDataRaw } from '@/hooks/Mapper/types/wormholes.ts';
import { CharacterTypeRaw } from '@/hooks/Mapper/types/character.ts'; import { CharacterTypeRaw } from '@/hooks/Mapper/types/character.ts';
import { RoutesList } from '@/hooks/Mapper/types/routes.ts'; import { RoutesList } from '@/hooks/Mapper/types/routes.ts';
import { Kill } from '@/hooks/Mapper/types/kills.ts'; import { Kill } from '@/hooks/Mapper/types/kills.ts';
import { UserPermissions } from '@/hooks/Mapper/types';
export enum Commands { export enum Commands {
init = 'init', init = 'init',
@@ -58,9 +59,10 @@ export type CommandInit = {
characters: CharacterTypeRaw[]; characters: CharacterTypeRaw[];
present_characters: string[]; present_characters: string[];
user_characters: string[]; user_characters: string[];
user_permissions: any; user_permissions: UserPermissions;
hubs: string[]; hubs: string[];
routes: RoutesList; routes: RoutesList;
options: Record<string, string | boolean>;
reset?: boolean; reset?: boolean;
}; };
export type CommandAddSystems = SolarSystemRawType[]; export type CommandAddSystems = SolarSystemRawType[];
@@ -118,7 +120,9 @@ export enum OutCommand {
getCharacterJumps = 'get_character_jumps', getCharacterJumps = 'get_character_jumps',
getSignatures = 'get_signatures', getSignatures = 'get_signatures',
getSystemStaticInfos = 'get_system_static_infos', getSystemStaticInfos = 'get_system_static_infos',
getConnectionInfo = 'get_connection_info',
updateConnectionTimeStatus = 'update_connection_time_status', updateConnectionTimeStatus = 'update_connection_time_status',
updateConnectionType = 'update_connection_type',
updateConnectionMassStatus = 'update_connection_mass_status', updateConnectionMassStatus = 'update_connection_mass_status',
updateConnectionShipSizeType = 'update_connection_ship_size_type', updateConnectionShipSizeType = 'update_connection_ship_size_type',
updateConnectionLocked = 'update_connection_locked', updateConnectionLocked = 'update_connection_locked',

View File

@@ -4,6 +4,7 @@ import { CharacterTypeRaw } from '@/hooks/Mapper/types/character.ts';
import { SolarSystemRawType } from '@/hooks/Mapper/types/system.ts'; import { SolarSystemRawType } from '@/hooks/Mapper/types/system.ts';
import { RoutesList } from '@/hooks/Mapper/types/routes.ts'; import { RoutesList } from '@/hooks/Mapper/types/routes.ts';
import { SolarSystemConnection } from '@/hooks/Mapper/types/connection.ts'; import { SolarSystemConnection } from '@/hooks/Mapper/types/connection.ts';
import { UserPermissions } from '@/hooks/Mapper/types';
export type MapUnionTypes = { export type MapUnionTypes = {
wormholesData: Record<string, WormholeDataRaw>; wormholesData: Record<string, WormholeDataRaw>;
@@ -17,4 +18,6 @@ export type MapUnionTypes = {
routes?: RoutesList; routes?: RoutesList;
kills: Record<number, number>; kills: Record<number, number>;
connections: SolarSystemConnection[]; connections: SolarSystemConnection[];
userPermissions: Partial<UserPermissions>;
options: Record<string, string | boolean>;
}; };

View File

@@ -0,0 +1,19 @@
export enum UserPermission {
ADMIN_MAP = 'admin_map',
MANAGE_MAP = 'manage_map',
VIEW_SYSTEM = 'view_system',
VIEW_CHARACTER = 'view_character',
VIEW_CONNECTION = 'view_connection',
ADD_SYSTEM = 'add_system',
ADD_CONNECTION = 'add_connection',
UPDATE_SYSTEM = 'update_system',
TRACK_CHARACTER = 'track_character',
DELETE_CONNECTION = 'delete_connection',
DELETE_SYSTEM = 'delete_system',
LOCK_SYSTEM = 'lock_system',
ADD_ACL = 'add_acl',
DELETE_ACL = 'delete_acl',
DELETE_MAP = 'delete_map',
}
export type UserPermissions = Record<UserPermission, boolean>;

View File

@@ -21,9 +21,12 @@ export type SystemSignature = {
eve_id: string; eve_id: string;
kind: string; kind: string;
name: string; name: string;
custom_info?: string;
description?: string; description?: string;
group: SignatureGroup; group: SignatureGroup;
type: string; type: string;
k162Type?: string;
linked_system?: SolarSystemStaticInfoRaw; linked_system?: SolarSystemStaticInfoRaw;
inserted_at?: string;
updated_at?: string; updated_at?: string;
}; };

View File

@@ -1,5 +1,7 @@
import { XYPosition } from 'reactflow'; import { XYPosition } from 'reactflow';
import { SystemSignature } from '@/hooks/Mapper/types/signatures';
export enum SolarSystemStaticInfoRawNames { export enum SolarSystemStaticInfoRawNames {
regionId = 'region_id', regionId = 'region_id',
constellationId = 'constellation_id', constellationId = 'constellation_id',
@@ -116,4 +118,5 @@ export type SolarSystemRawType = {
name: string | null; name: string | null;
system_static_info: SolarSystemStaticInfoRaw; system_static_info: SolarSystemStaticInfoRaw;
system_signatures: SystemSignature[];
}; };

View File

@@ -1,7 +1,6 @@
import { RefObject, useCallback } from 'react'; import { RefObject, useCallback } from 'react';
import { MapHandlers } from '@/hooks/Mapper/types/mapHandlers.ts'; import { MapHandlers } from '@/hooks/Mapper/types/mapHandlers.ts';
import { getQueryVariable } from './utils';
export const useMapperHandlers = (handlerRefs: RefObject<MapHandlers>[], hooksRef: RefObject<any>) => { export const useMapperHandlers = (handlerRefs: RefObject<MapHandlers>[], hooksRef: RefObject<any>) => {
const handleCommand = useCallback( const handleCommand = useCallback(
@@ -16,10 +15,6 @@ export const useMapperHandlers = (handlerRefs: RefObject<MapHandlers>[], hooksRe
); );
const handleMapEvent = useCallback(({ type, body }) => { const handleMapEvent = useCallback(({ type, body }) => {
if (getQueryVariable('debug') === 'true') {
console.log(type, body);
}
handlerRefs.forEach(ref => { handlerRefs.forEach(ref => {
if (!ref.current) { if (!ref.current) {
return; return;

View File

@@ -8,7 +8,7 @@ export default {
const selector = '#' + this.el.id; const selector = '#' + this.el.id;
const droppable = new Droppable(containers, { const droppable = new Droppable(containers, {
delay: 150, delay: 100,
draggable: '.draggable', draggable: '.draggable',
dropzone: '.dropzone', dropzone: '.dropzone',
mirror: { mirror: {

View File

@@ -4,13 +4,26 @@ export default {
mounted() { mounted() {
const hook = this; const hook = this;
const button = hook.el.querySelector('.update-button'); const refreshZone = hook.el.querySelector('#refresh-area');
button.addEventListener('click', function () { const handleUpdate = function (e: Event) {
const lastVersion = hook.el.dataset.version; const hexBricks = hook.el.querySelectorAll('.hex-brick');
localStorage.setItem(LAST_VERSION_KEY, lastVersion);
window.location.reload(); // Add a new class to each element
}); hexBricks.forEach(el => {
el.classList.add('hex-brick--active');
});
setTimeout(() => {
const lastVersion = hook.el.dataset.version;
localStorage.setItem(LAST_VERSION_KEY, lastVersion);
window.location.reload();
}, 2000);
};
refreshZone.addEventListener('click', handleUpdate);
refreshZone.addEventListener('mouseover', handleUpdate);
this.updated(); this.updated();
}, },

View File

@@ -13,8 +13,6 @@
}, },
"dependencies": { "dependencies": {
"@formkit/auto-animate": "0.7.0", "@formkit/auto-animate": "0.7.0",
"@react-rxjs/core": "^0.10.7",
"@react-rxjs/utils": "^0.9.7",
"@shopify/draggable": "^1.1.3", "@shopify/draggable": "^1.1.3",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"daisyui": "^4.11.1", "daisyui": "^4.11.1",
@@ -33,8 +31,7 @@
"react-grid-layout": "^1.3.4", "react-grid-layout": "^1.3.4",
"react-hook-form": "^7.53.1", "react-hook-form": "^7.53.1",
"react-usestateref": "^1.0.9", "react-usestateref": "^1.0.9",
"reactflow": "^11.10.4", "reactflow": "^11.11.4",
"rxjs": "^7.8.1",
"tailwindcss": "^3.3.6", "tailwindcss": "^3.3.6",
"topbar": "^3.0.0", "topbar": "^3.0.0",
"use-local-storage-state": "^19.3.1" "use-local-storage-state": "^19.3.1"

View File

@@ -469,19 +469,6 @@
resolved "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz" resolved "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz"
integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA== integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==
"@react-rxjs/core@^0.10.7":
version "0.10.7"
resolved "https://registry.npmjs.org/@react-rxjs/core/-/core-0.10.7.tgz"
integrity sha512-dornp8pUs9OcdqFKKRh9+I2FVe21gWufNun6RYU1ddts7kUy9i4Thvl0iqcPFbGY61cJQMAJF7dxixWMSD/A/A==
dependencies:
"@rx-state/core" "0.1.4"
use-sync-external-store "^1.0.0"
"@react-rxjs/utils@^0.9.7":
version "0.9.7"
resolved "https://registry.npmjs.org/@react-rxjs/utils/-/utils-0.9.7.tgz"
integrity sha512-m9CUTdRsglObvUAlYfB24QvN+QH4XqCGEKnCdSILIeOx7mMqSi9TTFp2zrj5XqtMiLnj4ReAdDxrXegLPB73bQ==
"@reactflow/background@11.3.14": "@reactflow/background@11.3.14":
version "11.3.14" version "11.3.14"
resolved "https://registry.npmjs.org/@reactflow/background/-/background-11.3.14.tgz" resolved "https://registry.npmjs.org/@reactflow/background/-/background-11.3.14.tgz"
@@ -650,11 +637,6 @@
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.17.2.tgz#5a2d08b81e8064b34242d5cc9973ef8dd1e60503" resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.17.2.tgz#5a2d08b81e8064b34242d5cc9973ef8dd1e60503"
integrity sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w== integrity sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==
"@rx-state/core@0.1.4":
version "0.1.4"
resolved "https://registry.npmjs.org/@rx-state/core/-/core-0.1.4.tgz"
integrity sha512-Z+3hjU2xh1HisLxt+W5hlYX/eGSDaXXP+ns82gq/PLZpkXLu0uwcNUh9RLY3Clq4zT+hSsA3vcpIGt6+UAb8rQ==
"@shopify/draggable@^1.1.3": "@shopify/draggable@^1.1.3":
version "1.1.3" version "1.1.3"
resolved "https://registry.npmjs.org/@shopify/draggable/-/draggable-1.1.3.tgz" resolved "https://registry.npmjs.org/@shopify/draggable/-/draggable-1.1.3.tgz"
@@ -3280,9 +3262,9 @@ react@18.2.0:
dependencies: dependencies:
loose-envify "^1.1.0" loose-envify "^1.1.0"
reactflow@^11.10.4: reactflow@^11.11.4:
version "11.11.4" version "11.11.4"
resolved "https://registry.npmjs.org/reactflow/-/reactflow-11.11.4.tgz" resolved "https://registry.yarnpkg.com/reactflow/-/reactflow-11.11.4.tgz#e3593e313420542caed81aecbd73fb9bc6576653"
integrity sha512-70FOtJkUWH3BAOsN+LU9lCrKoKbtOPnz2uq0CV2PLdNSwxTXOhCbsZr50GmZ+Rtw3jx8Uv7/vBFtCGixLfd4Og== integrity sha512-70FOtJkUWH3BAOsN+LU9lCrKoKbtOPnz2uq0CV2PLdNSwxTXOhCbsZr50GmZ+Rtw3jx8Uv7/vBFtCGixLfd4Og==
dependencies: dependencies:
"@reactflow/background" "11.3.14" "@reactflow/background" "11.3.14"
@@ -3421,13 +3403,6 @@ run-parallel@^1.1.9:
dependencies: dependencies:
queue-microtask "^1.2.2" queue-microtask "^1.2.2"
rxjs@^7.8.1:
version "7.8.1"
resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz"
integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==
dependencies:
tslib "^2.1.0"
safe-array-concat@^1.1.2: safe-array-concat@^1.1.2:
version "1.1.2" version "1.1.2"
resolved "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz" resolved "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz"
@@ -3732,7 +3707,7 @@ ts-interface-checker@^0.1.9:
resolved "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz" resolved "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz"
integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
tslib@^2.1.0, tslib@^2.6.2: tslib@^2.6.2:
version "2.6.2" version "2.6.2"
resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz" resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz"
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
@@ -3838,7 +3813,7 @@ use-local-storage-state@^19.3.1:
resolved "https://registry.npmjs.org/use-local-storage-state/-/use-local-storage-state-19.3.1.tgz" resolved "https://registry.npmjs.org/use-local-storage-state/-/use-local-storage-state-19.3.1.tgz"
integrity sha512-y3Z1dODXvZXZB4qtLDNN8iuXbsYD6TAxz61K58GWB9/yKwrNG9ynI0GzCTHi/Je1rMiyOwMimz0oyFsZn+Kj7Q== integrity sha512-y3Z1dODXvZXZB4qtLDNN8iuXbsYD6TAxz61K58GWB9/yKwrNG9ynI0GzCTHi/Je1rMiyOwMimz0oyFsZn+Kj7Q==
use-sync-external-store@1.2.0, use-sync-external-store@^1.0.0: use-sync-external-store@1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz" resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==

View File

@@ -138,6 +138,7 @@ config :ueberauth, WandererApp.Ueberauth.Strategy.Eve.OAuth,
System.get_env("EVE_CLIENT_WITH_CORP_WALLET_SECRET", "<EVE_CLIENT_WITH_CORP_WALLET_SECRET>") System.get_env("EVE_CLIENT_WITH_CORP_WALLET_SECRET", "<EVE_CLIENT_WITH_CORP_WALLET_SECRET>")
config :logger, config :logger,
truncate: :infinity,
level: level:
String.to_existing_atom( String.to_existing_atom(
System.get_env( System.get_env(
@@ -171,43 +172,65 @@ config :wanderer_app, WandererApp.Scheduler,
timeout: :infinity timeout: :infinity
if config_env() == :prod do if config_env() == :prod do
database_url = database_unix_socket =
System.get_env("DATABASE_URL") || System.get_env("DATABASE_UNIX_SOCKET")
raise """
environment variable DATABASE_URL is missing.
For example: ecto://USER:PASS@HOST/DATABASE
"""
maybe_ipv6 = database =
config_dir database_unix_socket
|> get_var_from_path_or_env("ECTO_IPV6", "false")
|> String.to_existing_atom()
|> case do |> case do
true -> [:inet6] nil ->
_ -> [] System.get_env("DATABASE_URL") ||
end raise """
environment variable DATABASE_URL is missing.
For example: ecto://USER:PASS@HOST/DATABASE
"""
db_ssl_enabled = _ ->
config_dir System.get_env("DATABASE_NAME") ||
|> get_var_from_path_or_env("DATABASE_SSL_ENABLED", "false") raise """
|> String.to_existing_atom() environment variable DATABASE_NAME is missing.
For example: "wanderer"
db_ssl_verify_none = """
config_dir
|> get_var_from_path_or_env("DATABASE_SSL_VERIFY_NONE", "false")
|> String.to_existing_atom()
client_opts =
if db_ssl_verify_none do
[verify: :verify_none]
end end
config :wanderer_app, WandererApp.Repo, config :wanderer_app, WandererApp.Repo,
url: database_url, pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
ssl: db_ssl_enabled,
ssl_opts: client_opts, if not is_nil(database_unix_socket) do
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"), config :wanderer_app, WandererApp.Repo,
socket_options: maybe_ipv6 socket_dir: database_unix_socket,
database: database
else
db_ssl_enabled =
config_dir
|> get_var_from_path_or_env("DATABASE_SSL_ENABLED", "false")
|> String.to_existing_atom()
db_ssl_verify_none =
config_dir
|> get_var_from_path_or_env("DATABASE_SSL_VERIFY_NONE", "false")
|> String.to_existing_atom()
client_opts =
if db_ssl_verify_none do
[verify: :verify_none]
end
maybe_ipv6 =
config_dir
|> get_var_from_path_or_env("ECTO_IPV6", "false")
|> String.to_existing_atom()
|> case do
true -> [:inet6]
_ -> []
end
config :wanderer_app, WandererApp.Repo,
url: database,
ssl: db_ssl_enabled,
ssl_opts: client_opts,
socket_options: maybe_ipv6
end
# The secret key base is used to sign/encrypt cookies and other secrets. # The secret key base is used to sign/encrypt cookies and other secrets.
# A default value is used in config/dev.exs and config/test.exs but you # A default value is used in config/dev.exs and config/test.exs but you

View File

@@ -3,9 +3,10 @@
# See https://fly.io/docs/reference/configuration/ for information about how to use this file. # See https://fly.io/docs/reference/configuration/ for information about how to use this file.
# #
app = 'wanderer' app = 'wanderer-test'
primary_region = 'ams' primary_region = 'ams'
kill_signal = 'SIGTERM' kill_signal = 'SIGTERM'
swap_size_mb = 512
[build] [build]
@@ -13,18 +14,14 @@ kill_signal = 'SIGTERM'
release_command = '/app/bin/migrate.sh' release_command = '/app/bin/migrate.sh'
[env] [env]
PHX_HOST = 'wanderer.fly.dev' PHX_HOST = 'wanderer-test.fly.dev'
PHX_SERVER = 'true' PHX_SERVER = 'true'
PORT = '8080' PORT = '8080'
[metrics]
port = 4021
path = "/metrics"
[http_service] [http_service]
internal_port = 8080 internal_port = 8080
force_https = true force_https = true
auto_stop_machines = false auto_stop_machines = 'off'
auto_start_machines = false auto_start_machines = false
min_machines_running = 0 min_machines_running = 0
processes = ['app'] processes = ['app']
@@ -35,6 +32,9 @@ path = "/metrics"
soft_limit = 1000 soft_limit = 1000
[[vm]] [[vm]]
memory = '1gb' size = 'shared-cpu-1x'
cpu_kind = 'shared'
cpus = 1 [[metrics]]
port = 4021
path = '/metrics'
https = false

View File

@@ -4,8 +4,6 @@ defmodule WandererApp.Api.Calculations.CalcMapPermissions do
use Ash.Resource.Calculation use Ash.Resource.Calculation
require Ash.Query require Ash.Query
import Bitwise
@impl true @impl true
def load(_query, _opts, _context) do def load(_query, _opts, _context) do
[ [
@@ -17,116 +15,8 @@ defmodule WandererApp.Api.Calculations.CalcMapPermissions do
end end
@impl true @impl true
def calculate([record], _opts, %{actor: actor}) do def calculate([record], _opts, %{actor: actor}),
characters = actor.characters do: WandererApp.Permissions.check_characters_access(actor.characters, record.acls)
character_ids = characters |> Enum.map(& &1.id)
character_eve_ids = characters |> Enum.map(& &1.eve_id)
character_corporation_ids =
characters |> Enum.map(& &1.corporation_id) |> Enum.map(&to_string/1)
character_alliance_ids = characters |> Enum.map(& &1.alliance_id) |> Enum.map(&to_string/1)
result =
record.acls
|> Enum.reduce([0, 0], fn acl, acc ->
is_owner? = acl.owner_id in character_ids
is_character_member? =
acl.members |> Enum.any?(fn member -> member.eve_character_id in character_eve_ids end)
is_corporation_member? =
acl.members
|> Enum.any?(fn member -> member.eve_corporation_id in character_corporation_ids end)
is_alliance_member? =
acl.members
|> Enum.any?(fn member -> member.eve_alliance_id in character_alliance_ids end)
if is_owner? || is_character_member? || is_corporation_member? || is_alliance_member? do
case acc do
[_, -1] ->
[-1, -1]
[-1, char_acc] ->
char_acl_mask =
acl.members
|> Enum.filter(fn member ->
member.eve_character_id in character_eve_ids
end)
|> Enum.reduce(0, fn member, acc ->
case acc do
-1 -> -1
_ -> WandererApp.Permissions.calc_role_mask(member.role, acc)
end
end)
char_acc =
case char_acl_mask do
-1 -> -1
_ -> char_acc ||| char_acl_mask
end
[-1, char_acc]
[any_acc, char_acc] ->
any_acl_mask =
acl.members
|> Enum.filter(fn member ->
member.eve_character_id in character_eve_ids ||
member.eve_corporation_id in character_corporation_ids ||
member.eve_alliance_id in character_alliance_ids
end)
|> Enum.reduce(0, fn member, acc ->
case acc do
-1 -> -1
_ -> WandererApp.Permissions.calc_role_mask(member.role, acc)
end
end)
char_acl_mask =
acl.members
|> Enum.filter(fn member ->
member.eve_character_id in character_eve_ids
end)
|> Enum.reduce(0, fn member, acc ->
case acc do
-1 -> -1
_ -> WandererApp.Permissions.calc_role_mask(member.role, acc)
end
end)
any_acc =
case any_acl_mask do
-1 -> -1
_ -> any_acc ||| any_acl_mask
end
char_acc =
case char_acl_mask do
-1 -> -1
_ -> char_acc ||| char_acl_mask
end
[any_acc, char_acc]
end
else
acc
end
end)
case result do
[_, -1] ->
[-1]
[-1, char_acc] ->
[char_acc]
[any_acc, _char_acc] ->
[any_acc]
end
end
@impl true @impl true
def calculate(_records, _opts, _context) do def calculate(_records, _opts, _context) do

View File

@@ -12,17 +12,22 @@ defmodule WandererApp.Api.MapCharacterSettings do
code_interface do code_interface do
define(:create, action: :create) define(:create, action: :create)
define(:destroy, action: :destroy)
define(:read_by_map, define(:read_by_map,
action: :read_by_map action: :read_by_map
) )
define(:tracked_by_map, define(:by_map_filtered,
action: :tracked_by_map action: :by_map_filtered
)
define(:tracked_by_map_filtered,
action: :tracked_by_map_filtered
) )
define(:tracked_by_map_all, define(:tracked_by_map_all,
action: :read_tracked_by_map action: :tracked_by_map_all
) )
define(:track, action: :track) define(:track, action: :track)
@@ -38,7 +43,14 @@ defmodule WandererApp.Api.MapCharacterSettings do
defaults [:create, :read, :update, :destroy] defaults [:create, :read, :update, :destroy]
read :tracked_by_map do read :by_map_filtered do
argument(:map_id, :string, allow_nil?: false)
argument(:character_ids, {:array, :uuid}, allow_nil?: false)
filter(expr(map_id == ^arg(:map_id) and character_id in ^arg(:character_ids)))
end
read :tracked_by_map_filtered do
argument(:map_id, :string, allow_nil?: false) argument(:map_id, :string, allow_nil?: false)
argument(:character_ids, {:array, :uuid}, allow_nil?: false) argument(:character_ids, {:array, :uuid}, allow_nil?: false)
@@ -52,7 +64,7 @@ defmodule WandererApp.Api.MapCharacterSettings do
filter(expr(map_id == ^arg(:map_id))) filter(expr(map_id == ^arg(:map_id)))
end end
read :read_tracked_by_map do read :tracked_by_map_all do
argument(:map_id, :string, allow_nil?: false) argument(:map_id, :string, allow_nil?: false)
filter(expr(map_id == ^arg(:map_id) and tracked == true)) filter(expr(map_id == ^arg(:map_id) and tracked == true))
end end

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