mirror of
				https://github.com/wanderer-industries/wanderer
				synced 2025-10-30 14:07:03 +00:00 
			
		
		
		
	Compare commits
	
		
			41 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 8a238a447d | ||
|   | 3731219216 | ||
|   | 73d5fd5f67 | ||
|   | e8e4aed6d5 | ||
|   | 63571a462f | ||
|   | 606add4142 | ||
|   | dac480b059 | ||
|   | 5f67cb1dd7 | ||
|   | 5886fff753 | ||
|   | da2e12bdd1 | ||
|   | 05c3d20e56 | ||
|   | 4633d26517 | ||
|   | 30b0556d47 | ||
|   | e094378dc5 | ||
|   | 0c48189503 | ||
|   | a5c346627a | ||
|   | 4e526040bf | ||
|   | 869c25cd60 | ||
|   | 6aac698cd8 | ||
|   | 230016b90f | ||
|   | 4b1aef8dd9 | ||
|   | d34509d7a0 | ||
|   | fca98ec232 | ||
|   | e2814e95bd | ||
|   | 68a3f84704 | ||
|   | 4bc76feefc | ||
|   | da39a55fd0 | ||
|   | ee3cf04cd4 | ||
|   | d79e7fe2ff | ||
|   | 8de9fdef32 | ||
|   | f51deeec2d | ||
|   | a971c69a96 | ||
|   | b7995f50de | ||
|   | 14997a2959 | ||
|   | 8fef6bcf82 | ||
|   | 1f82d23963 | ||
|   | 28317a2431 | ||
|   | 6aac496a57 | ||
|   | ac9306b713 | ||
|   | d55e804efa | ||
|   | 08407a5679 | 
							
								
								
									
										15
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @@ -78,22 +78,23 @@ jobs: | ||||
|           fetch-depth: 0 | ||||
|       - name: 😅 Cache deps | ||||
|         id: cache-deps | ||||
|         uses: actions/cache@v3 | ||||
|         uses: actions/cache@v4 | ||||
|         env: | ||||
|           cache-name: cache-elixir-deps | ||||
|         with: | ||||
|           path: deps | ||||
|           key: ${{ runner.os }}-mix-${{ env.cache-name }}-${{ hashFiles('**/mix.lock') }} | ||||
|           path: | | ||||
|             deps | ||||
|           key: ${{ runner.os }}-mix-${{ matrix.elixir }}-${{ matrix.otp }}-${{ hashFiles('**/mix.lock') }} | ||||
|           restore-keys: | | ||||
|             ${{ runner.os }}-mix-${{ env.cache-name }}- | ||||
|             ${{ runner.os }}-mix-${{ matrix.elixir }}-${{ matrix.otp }}- | ||||
|       - name: 😅 Cache compiled build | ||||
|         id: cache-build | ||||
|         uses: actions/cache@v3 | ||||
|         uses: actions/cache@v4 | ||||
|         env: | ||||
|           cache-name: cache-compiled-build | ||||
|         with: | ||||
|           path: | | ||||
|             **/_build | ||||
|             _build | ||||
|           key: ${{ runner.os }}-build-${{ hashFiles('**/mix.lock') }}-${{ hashFiles( '**/lib/**/*.{ex,eex}', '**/config/*.exs', '**/mix.exs' ) }} | ||||
|           restore-keys: | | ||||
|             ${{ runner.os }}-build-${{ hashFiles('**/mix.lock') }}- | ||||
| @@ -187,6 +188,8 @@ jobs: | ||||
|           push: true | ||||
|           context: . | ||||
|           file: ./Dockerfile | ||||
|           cache-from: type=gha | ||||
|           cache-to: type=gha,mode=max | ||||
|           labels: ${{ steps.meta.outputs.labels }} | ||||
|           platforms: ${{ matrix.platform }} | ||||
|           outputs: type=image,"name=${{ env.REGISTRY_IMAGE }}",push-by-digest=true,name-canonical=true,push=true | ||||
|   | ||||
							
								
								
									
										152
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										152
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -2,6 +2,158 @@ | ||||
|  | ||||
| <!-- changelog --> | ||||
|  | ||||
| ## [v1.43.8](https://github.com/wanderer-industries/wanderer/compare/v1.43.7...v1.43.8) (2025-01-26) | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ### Bug Fixes: | ||||
|  | ||||
| * Core: Update shuttered constellations (required EVE DB data update on server). | ||||
|  | ||||
| ## [v1.43.7](https://github.com/wanderer-industries/wanderer/compare/v1.43.6...v1.43.7) (2025-01-26) | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ## [v1.43.6](https://github.com/wanderer-industries/wanderer/compare/v1.43.5...v1.43.6) (2025-01-22) | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ### Bug Fixes: | ||||
|  | ||||
| * Widgets: Fix widgets not visible on map | ||||
|  | ||||
| ## [v1.43.5](https://github.com/wanderer-industries/wanderer/compare/v1.43.4...v1.43.5) (2025-01-22) | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ### Bug Fixes: | ||||
|  | ||||
| * Audit: Fix signature added/removed system name | ||||
|  | ||||
| ## [v1.43.4](https://github.com/wanderer-industries/wanderer/compare/v1.43.3...v1.43.4) (2025-01-21) | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ### Bug Fixes: | ||||
|  | ||||
| * improve structure widget styling (#127) | ||||
|  | ||||
| ## [v1.43.3](https://github.com/wanderer-industries/wanderer/compare/v1.43.2...v1.43.3) (2025-01-21) | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ## [v1.43.2](https://github.com/wanderer-industries/wanderer/compare/v1.43.1...v1.43.2) (2025-01-21) | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ### Bug Fixes: | ||||
|  | ||||
| * prevent constraint error for follow/toggle (#132) | ||||
|  | ||||
| ## [v1.43.1](https://github.com/wanderer-industries/wanderer/compare/v1.43.0...v1.43.1) (2025-01-20) | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ## [v1.43.0](https://github.com/wanderer-industries/wanderer/compare/v1.42.5...v1.43.0) (2025-01-20) | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ### Features: | ||||
|  | ||||
| * add news post for structures widget (#131) | ||||
|  | ||||
| ## [v1.42.5](https://github.com/wanderer-industries/wanderer/compare/v1.42.4...v1.42.5) (2025-01-20) | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ### Bug Fixes: | ||||
|  | ||||
| * Map: Fix link signatures on splash. Fix deleting connection on locked system remove. | ||||
|  | ||||
| ## [v1.42.4](https://github.com/wanderer-industries/wanderer/compare/v1.42.3...v1.42.4) (2025-01-20) | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ### Bug Fixes: | ||||
|  | ||||
| * Fix system statics list (required EVE DB data update). Add system name to signature added/removed audit log | ||||
|  | ||||
| ## [v1.42.3](https://github.com/wanderer-industries/wanderer/compare/v1.42.2...v1.42.3) (2025-01-17) | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ### Bug Fixes: | ||||
|  | ||||
| * change structure tooltip to avoid paste confusion (#125) | ||||
|  | ||||
| * change structure tooltip to avoid paste confusion | ||||
|  | ||||
| * clarify use of evetime and use primereact calendar | ||||
|  | ||||
| ## [v1.42.2](https://github.com/wanderer-industries/wanderer/compare/v1.42.1...v1.42.2) (2025-01-16) | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ## [v1.42.1](https://github.com/wanderer-industries/wanderer/compare/v1.42.0...v1.42.1) (2025-01-16) | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ### Bug Fixes: | ||||
|  | ||||
| * Map: Remove linked sig ID if system containing signature removed from map | ||||
|  | ||||
| ## [v1.42.0](https://github.com/wanderer-industries/wanderer/compare/v1.41.0...v1.42.0) (2025-01-16) | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ### Features: | ||||
|  | ||||
| * Audit: Add 'Signatures added/removed' map audit events | ||||
|  | ||||
| ## [v1.41.0](https://github.com/wanderer-industries/wanderer/compare/v1.40.7...v1.41.0) (2025-01-16) | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ### Features: | ||||
|  | ||||
| * Audit: Add 'ACL added/removed' map audit events | ||||
|  | ||||
| ## [v1.40.7](https://github.com/wanderer-industries/wanderer/compare/v1.40.6...v1.40.7) (2025-01-15) | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ## [v1.40.6](https://github.com/wanderer-industries/wanderer/compare/v1.40.5...v1.40.6) (2025-01-15) | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ### Bug Fixes: | ||||
|  | ||||
| * Map: Fix follow mode | ||||
|  | ||||
| * center system is not selected text for structures (#122) | ||||
|  | ||||
| * Map: Fix system revert issues | ||||
|  | ||||
| * Map: Fix issues with splashing signatures select & sig ID in temp names | ||||
|  | ||||
| ## [v1.40.5](https://github.com/wanderer-industries/wanderer/compare/v1.40.4...v1.40.5) (2025-01-14) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -112,3 +112,28 @@ | ||||
| .p-autocomplete .p-autocomplete-multiple-container .p-autocomplete-token { | ||||
|   margin-right: 0 !important; | ||||
| } | ||||
|  | ||||
| /* Fixed sizes of Input switch */ | ||||
| .p-inputswitch { | ||||
|   width: 2.0rem; | ||||
|   height: 1.15rem; | ||||
|  | ||||
|   .p-inputswitch-slider:before { | ||||
|     width: 0.8rem; | ||||
|     height: 0.8rem; | ||||
|     left: 0.14rem; | ||||
|     margin-top: -0.385rem; | ||||
|   } | ||||
|  | ||||
|   &.p-highlight .p-inputswitch-slider:before { | ||||
|     transform: translateX(0.8rem); | ||||
|   } | ||||
|  | ||||
|   &:not(.p-disabled):has(.p-inputswitch-input:hover) .p-inputswitch-slider { | ||||
|     background: rgb(255 255 255 / 21%); | ||||
|   } | ||||
|  | ||||
|   &.p-highlight .p-inputswitch-slider { | ||||
|     background: #966d3d; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -2,6 +2,7 @@ import { ForwardedRef, forwardRef, MouseEvent, useCallback, useEffect, useMemo } | ||||
| import ReactFlow, { | ||||
|   Background, | ||||
|   Edge, | ||||
|   EdgeChange, | ||||
|   MiniMap, | ||||
|   Node, | ||||
|   NodeChange, | ||||
| @@ -83,6 +84,7 @@ interface MapCompProps { | ||||
|   onCommand: OutCommandHandler; | ||||
|   onSelectionChange: OnMapSelectionChange; | ||||
|   onManualDelete(systems: string[]): void; | ||||
|   canRemoveConnection?(connectionId: string): boolean; | ||||
|   onConnectionInfoClick?(e: SolarSystemConnection): void; | ||||
|   onAddSystem?: OnMapAddSystemCallback; | ||||
|   onSelectionContextMenu?: NodeSelectionMouseHandler; | ||||
| @@ -112,8 +114,9 @@ const MapComp = ({ | ||||
|   isSoftBackground, | ||||
|   theme, | ||||
|   onAddSystem, | ||||
|   canRemoveConnection, | ||||
| }: MapCompProps) => { | ||||
|   const { getNode, getNodes } = useReactFlow(); | ||||
|   const { getEdge, getNode, getNodes } = useReactFlow(); | ||||
|   const [nodes, , onNodesChange] = useNodesState<Node<SolarSystemRawType>>(initialNodes); | ||||
|   const [edges, , onEdgesChange] = useEdgesState<Edge<SolarSystemConnection>>(initialEdges); | ||||
|  | ||||
| @@ -222,6 +225,40 @@ const MapComp = ({ | ||||
|     [getNode, getNodes, onManualDelete, onNodesChange], | ||||
|   ); | ||||
|  | ||||
|   const handleEdgesChange = useCallback( | ||||
|     (changes: EdgeChange[]) => { | ||||
|       const nextChanges = changes.reduce((acc, change) => { | ||||
|         if (change.type !== 'remove') { | ||||
|           return [...acc, change]; | ||||
|         } | ||||
|  | ||||
|         if (canRemoveConnection?.(change.id)) { | ||||
|           return [...acc, change]; | ||||
|         } | ||||
|  | ||||
|         const edge = getEdge(change.id); | ||||
|         if (!edge) { | ||||
|           return [...acc, change]; | ||||
|         } | ||||
|  | ||||
|         const sourceNode = getNode(edge.source); | ||||
|         const targetNode = getNode(edge.target); | ||||
|         if (!sourceNode || !targetNode) { | ||||
|           return [...acc, change]; | ||||
|         } | ||||
|  | ||||
|         if (sourceNode.data.locked || targetNode.data.locked) { | ||||
|           return acc; | ||||
|         } | ||||
|  | ||||
|         return [...acc, change]; | ||||
|       }, [] as EdgeChange[]); | ||||
|  | ||||
|       onEdgesChange(nextChanges); | ||||
|     }, | ||||
|     [getEdge, getNode, onEdgesChange], | ||||
|   ); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     update(x => ({ | ||||
|       ...x, | ||||
| @@ -237,7 +274,7 @@ const MapComp = ({ | ||||
|           nodes={nodes} | ||||
|           edges={edges} | ||||
|           onNodesChange={handleNodesChange} | ||||
|           onEdgesChange={onEdgesChange} | ||||
|           onEdgesChange={handleEdgesChange} | ||||
|           onConnect={onConnect} | ||||
|           // TODO we need save into session all of this | ||||
|           //      and on any action do either | ||||
|   | ||||
| @@ -13,7 +13,6 @@ import { | ||||
| import { WormholeClassComp } from '@/hooks/Mapper/components/map/components/WormholeClassComp'; | ||||
| import { UnsplashedSignature } from '@/hooks/Mapper/components/map/components/UnsplashedSignature'; | ||||
|  | ||||
| // eslint-disable-next-line react/display-name | ||||
| export const SolarSystemNodeDefault = memo((props: NodeProps<MapSolarSystemType>) => { | ||||
|   const nodeVars = useSolarSystemNode(props); | ||||
|  | ||||
|   | ||||
| @@ -1,274 +1,39 @@ | ||||
| @import '@/hooks/Mapper/components/map/styles/eve-common-variables'; | ||||
| @import './SolarSystemNodeDefault.module.scss'; | ||||
|  | ||||
| $pastel-blue: #5a7d9a; | ||||
| $pastel-pink: #d291bc; | ||||
| $pastel-green: #88b04b; | ||||
| $pastel-yellow: #ffdd59; | ||||
| $dark-bg: #2d2d2d; | ||||
| $text-color: #ffffff; | ||||
| $tooltip-bg: #202020; | ||||
| /* --------------------------- | ||||
|    Only override what's different | ||||
| --------------------------- */ | ||||
|  | ||||
| /* 1) .RootCustomNode: | ||||
|    - new background-color using CSS var | ||||
|    - plus color, font-family, and font-weight */ | ||||
| .RootCustomNode { | ||||
|   display: flex; | ||||
|   width: 130px; | ||||
|   height: 34px; | ||||
|  | ||||
|   flex-direction: column; | ||||
|   padding: 2px 6px; | ||||
|   font-size: 10px; | ||||
|  | ||||
|   background-color: var(--rf-node-bg-color, #202020) !important; | ||||
|   color: var(--rf-text-color, #ffffff); | ||||
|   font-family: var(--rf-node-font-family, inherit) !important; | ||||
|   font-weight: var(--rf-node-font-weight, inherit) !important; | ||||
|  | ||||
|   box-shadow: 0 0 5px rgba($dark-bg, 0.5); | ||||
|   border: 1px solid darken($pastel-blue, 10%); | ||||
|   border-radius: 5px; | ||||
|   position: relative; | ||||
|   z-index: 1; | ||||
|   overflow: hidden; | ||||
|  | ||||
|   &.Mataria, | ||||
|   &.Amarria, | ||||
|   &.Gallente, | ||||
|   &.Caldaria { | ||||
|     &::before { | ||||
|       content: ''; | ||||
|       position: absolute; | ||||
|       top: 0; | ||||
|       left: 0; | ||||
|       right: 0; | ||||
|       bottom: 0; | ||||
|       background-size: cover; | ||||
|       background-position: 50% 50%; | ||||
|       z-index: -1; | ||||
|       background-repeat: no-repeat; | ||||
|       border-radius: 3px; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   &.Mataria { | ||||
|     &::before { | ||||
|       background-image: url('/images/mataria-180.png'); | ||||
|       opacity: 0.6; | ||||
|       background-position-x: 1px; | ||||
|       background-position-y: -14px; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   &.Caldaria { | ||||
|     &::before { | ||||
|       background-image: url('/images/caldaria-180.png'); | ||||
|       opacity: 0.6; | ||||
|       background-position-x: 1px; | ||||
|       background-position-y: -10px; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   &.Amarria { | ||||
|     &::before { | ||||
|       opacity: 0.45; | ||||
|       background-image: url('/images/amarr-180.png'); | ||||
|       background-position-x: 0; | ||||
|       background-position-y: -13px; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   &.Gallente { | ||||
|     &::before { | ||||
|       opacity: 0.5; | ||||
|       background-image: url('/images/gallente-180.png'); | ||||
|       background-position-x: 1px; | ||||
|       background-position-y: 0; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   &.selected { | ||||
|     border-color: $pastel-pink; | ||||
|     box-shadow: 0 0 10px #9a1af1c2; | ||||
|   } | ||||
|  | ||||
|   &.tooltip { | ||||
|     background-color: $tooltip-bg; | ||||
|     color: $text-color; | ||||
|     padding: 5px 10px; | ||||
|     border-radius: 3px; | ||||
|     border: 1px solid $pastel-pink; | ||||
|   } | ||||
|  | ||||
|   &.eve-system-status-home { | ||||
|     border: 1px solid var(--eve-solar-system-status-color-home-dark30); | ||||
|     background-image: linear-gradient( | ||||
|       275deg, | ||||
|       var(--eve-solar-system-status-home), | ||||
|       transparent | ||||
|     ); | ||||
|     &.selected { | ||||
|       border-color: var(--eve-solar-system-status-color-home); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   &.eve-system-status-friendly { | ||||
|     border: 1px solid var(--eve-solar-system-status-color-friendly-dark20); | ||||
|     background-image: linear-gradient( | ||||
|       275deg, | ||||
|       var(--eve-solar-system-status-friendly-dark30), | ||||
|       transparent | ||||
|     ); | ||||
|     &.selected { | ||||
|       border-color: var(--eve-solar-system-status-color-friendly-dark5); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   &.eve-system-status-lookingFor { | ||||
|     border: 1px solid var(--eve-solar-system-status-color-lookingFor-dark15); | ||||
|     background-image: linear-gradient(275deg, #45ff8f2f, #457fff2f); | ||||
|     &.selected { | ||||
|       border-color: $pastel-pink; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   &.eve-system-status-warning { | ||||
|     background-image: linear-gradient( | ||||
|       275deg, | ||||
|       var(--eve-solar-system-status-warning), | ||||
|       transparent | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   &.eve-system-status-dangerous { | ||||
|     background-image: linear-gradient( | ||||
|       275deg, | ||||
|       var(--eve-solar-system-status-dangerous), | ||||
|       transparent | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   &.eve-system-status-target { | ||||
|     background-image: linear-gradient( | ||||
|       275deg, | ||||
|       var(--eve-solar-system-status-target), | ||||
|       transparent | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| /* 2) .Bookmarks: | ||||
|    - add var-based font family/weight | ||||
| */ | ||||
| .Bookmarks { | ||||
|   position: absolute; | ||||
|   width: 100%; | ||||
|   z-index: 1; | ||||
|   display: flex; | ||||
|   left: 4px; | ||||
|   font-family: var(--rf-node-font-family, inherit) !important; | ||||
|   font-weight: var(--rf-node-font-weight, inherit) !important; | ||||
|  | ||||
|  | ||||
|   & > .Bookmark { | ||||
|     min-width: 13px; | ||||
|     height: 22px; | ||||
|     position: relative; | ||||
|     top: -13px; | ||||
|     border-radius: 5px; | ||||
|     color: #ffffff; | ||||
|     font-size: 8px; | ||||
|     text-align: center; | ||||
|     padding-top: 2px; | ||||
|     font-weight: bolder; | ||||
|     padding-left: 3px; | ||||
|     padding-right: 3px; | ||||
|  | ||||
|     //background-color: #833ca4; | ||||
|  | ||||
|     &:not(:first-child) { | ||||
|       box-shadow: inset 4px -3px 4px rgba(0, 0, 0, 0.3); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .BookmarkWithIcon { | ||||
|     display: flex; | ||||
|     justify-content: center; | ||||
|     align-items: center; | ||||
|     margin-top: -2px; | ||||
|     text-shadow: 0 0 3px rgba(0, 0, 0, 1); | ||||
|     padding-right: 2px; | ||||
|  | ||||
|     & > .icon { | ||||
|       width: 8px; | ||||
|       height: 8px; | ||||
|       font-size: 8px; | ||||
|     } | ||||
|  | ||||
|     & > .text { | ||||
|       margin-top: 1px; | ||||
|       font-size: 9px; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| .Unsplashed { | ||||
|   position: absolute; | ||||
|   width: calc(50% - 4px); | ||||
|   z-index: -1; | ||||
|   display: flex; | ||||
|   flex-wrap: wrap; | ||||
|   gap: 2px; | ||||
|   left: 2px; | ||||
|  | ||||
|   &--right { | ||||
|     left: calc(50% + 6px); | ||||
|   } | ||||
|  | ||||
|   & > .Signature { | ||||
|     width: 13px; | ||||
|     height: 4px; | ||||
|     position: relative; | ||||
|     top: 3px; | ||||
|     border-radius: 5px; | ||||
|     color: #ffffff; | ||||
|     font-size: 8px; | ||||
|     text-align: center; | ||||
|     padding-top: 2px; | ||||
|     font-weight: bolder; | ||||
|     padding-left: 3px; | ||||
|     padding-right: 3px; | ||||
|     display: block; | ||||
|  | ||||
|     background-color: #833ca4; | ||||
|  | ||||
|     &:not(:first-child) { | ||||
|       box-shadow: inset 4px -3px 4px rgba(0, 0, 0, 0.3); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| .icon { | ||||
|   width: 8px; | ||||
|   height: 8px; | ||||
|   font-size: 8px; | ||||
| } | ||||
|  | ||||
| /* 3) .HeadRow, .classTitle, .classSystemName: | ||||
|    - add new references to var-based font family/weight | ||||
| */ | ||||
| .HeadRow { | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   gap: 3px; | ||||
|   font-size: 11px; | ||||
|   line-height: 14px; | ||||
|   font-weight: 500; | ||||
|   position: relative; | ||||
|   top: 1px; | ||||
|   font-family: var(--rf-node-font-family, inherit) !important; | ||||
|   font-weight: var(--rf-node-font-weight, inherit) !important; | ||||
|  | ||||
|   .classTitle { | ||||
|     font-size: 11px; | ||||
|     font-family: var(--rf-node-font-family, inherit) !important; | ||||
|     font-weight: var(--rf-node-font-weight, inherit) !important; | ||||
|     text-shadow: 0 0 2px rgb(0 0 0 / 73%); | ||||
|   } | ||||
|  | ||||
|  | ||||
|   /* Firefox kostyl */ | ||||
|   @-moz-document url-prefix() { | ||||
|     .classSystemName { | ||||
|       font-family: var(--rf-node-font-family, inherit) !important; | ||||
| @@ -280,20 +45,15 @@ $tooltip-bg: #202020; | ||||
|     font-family: var(--rf-node-font-family, inherit) !important; | ||||
|     font-weight: var(--rf-node-font-weight, inherit) !important; | ||||
|   } | ||||
|  | ||||
|   .solarSystemName { | ||||
|   } | ||||
| } | ||||
|  | ||||
| /* 4) .BottomRow: | ||||
|    - introduces .tagTitle, .regionName, .customName, .localCounter | ||||
|      referencing new CSS variables */ | ||||
| .BottomRow { | ||||
|   display: flex; | ||||
|   justify-content: space-between; | ||||
|   align-items: center; | ||||
|   height: 19px; | ||||
|   font-family: var(--rf-node-font-family, inherit) !important; | ||||
|   font-weight: var(--rf-node-font-weight, inherit) !important; | ||||
|  | ||||
|  | ||||
|   .tagTitle { | ||||
|     font-size: 11px; | ||||
|     font-weight: medium; | ||||
| @@ -327,100 +87,5 @@ $tooltip-bg: #202020; | ||||
|       font-family: var(--rf-node-font-family, inherit) !important; | ||||
|       font-weight: var(--rf-node-font-weight, inherit) !important; | ||||
|     } | ||||
|  | ||||
|     & > i { | ||||
|       position: relative; | ||||
|       top: 1px; | ||||
|     } | ||||
|  | ||||
|     & > span { | ||||
|       font-size: 9px; | ||||
|       line-height: 9px; | ||||
|       font-weight: 500; | ||||
|       //margin-top: 1px; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| .effect { | ||||
|   width: 8px; | ||||
|   height: 8px; | ||||
|   margin-top: -2px; | ||||
|   box-sizing: border-box; | ||||
|   border-radius: 2px; | ||||
|   margin-left: 1px; | ||||
| } | ||||
|  | ||||
| .statics { | ||||
|   display: flex; | ||||
|   gap: 2px; | ||||
|   font-size: 8px; | ||||
|  | ||||
|   & > * { | ||||
|     line-height: 10px; | ||||
|   } | ||||
|  | ||||
|   /* Firefox kostyl */ | ||||
|   @-moz-document url-prefix() { | ||||
|     position: relative; | ||||
|     top: -1px; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .Handlers { | ||||
|   position: absolute; | ||||
|   z-index: 2; | ||||
|   top: 0; | ||||
|   left: 0; | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
| } | ||||
|  | ||||
| .Handle { | ||||
|   min-width: initial; | ||||
|   min-height: initial; | ||||
|   border: 1px solid $pastel-blue; | ||||
|   width: 5px; | ||||
|   height: 5px; | ||||
|  | ||||
|   &.selected { | ||||
|     border-color: $pastel-pink; | ||||
|   } | ||||
|  | ||||
|   &.HandleTop { | ||||
|     top: -2px; | ||||
|   } | ||||
|  | ||||
|   &.HandleRight { | ||||
|     right: -2px; | ||||
|   } | ||||
|  | ||||
|   &.HandleBottom { | ||||
|     bottom: -2px; | ||||
|   } | ||||
|  | ||||
|   &.HandleLeft { | ||||
|     left: -2px; | ||||
|   } | ||||
|  | ||||
|   &.Tick { | ||||
|     width: 7px; | ||||
|     height: 7px; | ||||
|  | ||||
|     &.HandleTop { | ||||
|       top: -3px; | ||||
|     } | ||||
|  | ||||
|     &.HandleRight { | ||||
|       right: -3px; | ||||
|     } | ||||
|  | ||||
|     &.HandleBottom { | ||||
|       bottom: -3px; | ||||
|     } | ||||
|  | ||||
|     &.HandleLeft { | ||||
|       left: -3px; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -15,4 +15,8 @@ | ||||
|     font-weight: bolder; | ||||
|     display: block; | ||||
|   } | ||||
|  | ||||
|   & > .Eol { | ||||
|     display: block; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -8,8 +8,8 @@ import { WORMHOLE_CLASS_STYLES, WORMHOLES_ADDITIONAL_INFO } from '@/hooks/Mapper | ||||
| 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'; | ||||
| import { K162_TYPES_MAP } from '@/hooks/Mapper/constants.ts'; | ||||
| import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo.ts'; | ||||
|  | ||||
| interface UnsplashedSignatureProps { | ||||
|   signature: SystemSignature; | ||||
| @@ -22,17 +22,22 @@ export const UnsplashedSignature = ({ signature }: UnsplashedSignatureProps) => | ||||
|   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); | ||||
|   const customInfo = useMemo(() => { | ||||
|     return parseSignatureCustomInfo(signature.custom_info); | ||||
|   }, [signature]); | ||||
|  | ||||
|   const k162TypeOption = useMemo(() => { | ||||
|     if (!customInfo?.k162Type) { | ||||
|       return null; | ||||
|     } | ||||
|  | ||||
|     return K162_TYPES_MAP[customInfo.k162Type]; | ||||
|   }, [customInfo]); | ||||
|  | ||||
|   const isEOL = useMemo(() => { | ||||
|     return customInfo?.isEOL; | ||||
|   }, [customInfo]); | ||||
|  | ||||
|   const whClassStyle = useMemo(() => { | ||||
|     if (signature.type === 'K162' && k162TypeOption) { | ||||
|       const k162Data = wormholesData[k162TypeOption.whClassName]; | ||||
| @@ -45,19 +50,19 @@ export const UnsplashedSignature = ({ signature }: UnsplashedSignatureProps) => | ||||
|   return ( | ||||
|     <WdTooltipWrapper | ||||
|       className={clsx(classes.Signature)} | ||||
|       // @ts-ignore | ||||
|       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="flex flex-col gap-1"> | ||||
|           <InfoDrawer title={<b className="text-slate-50">{signature.eve_id}</b>}> | ||||
|             {renderInfoColumn(signature)} | ||||
|           </InfoDrawer> | ||||
|         </div> | ||||
|       } | ||||
|     > | ||||
|       <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 width="13" height="8" viewBox="0 0 13 8" xmlns="http://www.w3.org/2000/svg"> | ||||
|           <rect y="1" width="13" height="4" rx="2" className={whClassStyle} fill="currentColor" /> | ||||
|           {isEOL && <rect x="4" width="5" height="6" rx="1" className={clsx(classes.Eol)} fill="#a153ac" />} | ||||
|         </svg> | ||||
|       </div> | ||||
|     </WdTooltipWrapper> | ||||
|   | ||||
| @@ -70,7 +70,7 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange | ||||
|               setTimeout(() => addConnections(data as CommandAddConnections), 100); | ||||
|               break; | ||||
|             case Commands.removeConnections: | ||||
|               removeConnections(data as CommandRemoveConnections); | ||||
|               setTimeout(() => removeConnections(data as CommandRemoveConnections), 100); | ||||
|               break; | ||||
|             case Commands.charactersUpdated: | ||||
|               charactersUpdated(data as CommandCharactersUpdated); | ||||
|   | ||||
| @@ -10,7 +10,7 @@ import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormhol | ||||
| import { getSystemClassStyles, prepareUnsplashedChunks } from '@/hooks/Mapper/components/map/helpers'; | ||||
| import { sortWHClasses } from '@/hooks/Mapper/helpers'; | ||||
| import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager'; | ||||
| import { OutCommand } from '@/hooks/Mapper/types'; | ||||
| import { CharacterTypeRaw, OutCommand } from '@/hooks/Mapper/types'; | ||||
| import { LABELS_INFO, LABELS_ORDER } from '@/hooks/Mapper/components/map/constants'; | ||||
|  | ||||
| function getActivityType(count: number) { | ||||
| @@ -155,7 +155,7 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>) { | ||||
|     return solar_system_name; | ||||
|   }, [isTempSystemNameEnabled, solar_system_name, temporaryName]); | ||||
|  | ||||
|   const customName = (isTempSystemNameEnabled && temporary_name && name) || (solar_system_name !== name && name); | ||||
|   const customName = (isTempSystemNameEnabled && temporaryName && name) || (solar_system_name !== name && name) || null; | ||||
|  | ||||
|   const [unsplashedLeft, unsplashedRight] = useMemo(() => { | ||||
|     if (!isShowUnsplashedSignatures) { | ||||
| @@ -212,3 +212,43 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>) { | ||||
|  | ||||
|   return nodeVars; | ||||
| } | ||||
|  | ||||
| export interface SolarSystemNodeVars { | ||||
|   id: string; | ||||
|   selected: boolean; | ||||
|   visible: boolean; | ||||
|   isWormhole: boolean; | ||||
|   classTitleColor: string | null; | ||||
|   killsCount: number | null; | ||||
|   killsActivityType: string | null; | ||||
|   hasUserCharacters: boolean; | ||||
|   showHandlers: boolean; | ||||
|   regionClass: string | null; | ||||
|   systemName: string; | ||||
|   customName?: string | null; | ||||
|   labelCustom: string | null; | ||||
|   isShattered: boolean; | ||||
|   tag?: string | null; | ||||
|   status?: number; | ||||
|   // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||
|   labelsInfo: Array<any>; | ||||
|   dbClick: (event?: void) => void; | ||||
|   sortedStatics: Array<string | number>; | ||||
|   effectName: string | null; | ||||
|   regionName: string | null; | ||||
|   solarSystemId: number; | ||||
|   solarSystemName: string | null; | ||||
|   locked: boolean; | ||||
|   hubs: string[] | number[]; | ||||
|   name: string | null; | ||||
|   isConnecting: boolean; | ||||
|   hoverNodeId: string | null; | ||||
|   charactersInSystem: Array<CharacterTypeRaw>; | ||||
|   // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||
|   unsplashedLeft: Array<any>; | ||||
|   // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||
|   unsplashedRight: Array<any>; | ||||
|   isThickConnections: boolean; | ||||
|   classTitle: string | null; | ||||
|   temporaryName?: string | null; | ||||
| } | ||||
|   | ||||
| @@ -1,4 +1,3 @@ | ||||
|  | ||||
| @import './eve-common-variables'; | ||||
| @import './eve-common'; | ||||
| @import url('https://fonts.googleapis.com/css2?family=Oxygen:wght@300;400;700&display=swap'); | ||||
| @@ -40,7 +39,6 @@ | ||||
|   --eve-wh-type-color-c13: #7986cb; | ||||
|   --eve-wh-type-color-drifter: #44aa82; | ||||
|  | ||||
|  | ||||
|   --rf-node-font-weight: bold; | ||||
|   --rf-node-line-height: normal; | ||||
|   --rf-node-font-family: 'Oxygen', sans-serif; | ||||
|   | ||||
| @@ -1,28 +1,29 @@ | ||||
| import { PrimeIcons } from 'primereact/api'; | ||||
| import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types'; | ||||
| import { SystemViewStandalone, WHClassView } from '@/hooks/Mapper/components/ui-kit'; | ||||
| import { SystemViewStandalone, TooltipPosition, WHClassView } from '@/hooks/Mapper/components/ui-kit'; | ||||
|  | ||||
| import { | ||||
|   k162Types, | ||||
|   renderK162Type, | ||||
| } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureK162TypeSelect'; | ||||
| import { renderK162Type } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureK162TypeSelect'; | ||||
| import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper'; | ||||
|  | ||||
| import clsx from 'clsx'; | ||||
| import { renderName } from './renderName.tsx'; | ||||
| import { K162_TYPES_MAP } from '@/hooks/Mapper/constants.ts'; | ||||
| import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo.ts'; | ||||
|  | ||||
| export const renderInfoColumn = (row: SystemSignature) => { | ||||
|   if (!row.group || row.group === SignatureGroup.Wormhole) { | ||||
|     let k162TypeOption = null; | ||||
|     if (row.custom_info) { | ||||
|       const customInfo = JSON.parse(row.custom_info); | ||||
|       if (customInfo.k162Type) { | ||||
|         k162TypeOption = k162Types.find(x => x.value === customInfo.k162Type); | ||||
|       } | ||||
|     } | ||||
|     const customInfo = parseSignatureCustomInfo(row.custom_info); | ||||
|  | ||||
|     const k162TypeOption = customInfo.k162Type ? K162_TYPES_MAP[customInfo.k162Type] : null; | ||||
|  | ||||
|     return ( | ||||
|       <div className="flex justify-start items-center gap-[4px]"> | ||||
|         {customInfo.isEOL && ( | ||||
|           <WdTooltipWrapper offset={5} position={TooltipPosition.top} content="Signature marked as EOL"> | ||||
|             <div className="pi pi-clock text-fuchsia-400 text-[11px] mr-[2px]"></div> | ||||
|           </WdTooltipWrapper> | ||||
|         )} | ||||
|  | ||||
|         {row.type && ( | ||||
|           <WHClassView | ||||
|             className="text-[11px]" | ||||
| @@ -34,7 +35,7 @@ export const renderInfoColumn = (row: SystemSignature) => { | ||||
|           /> | ||||
|         )} | ||||
|  | ||||
|         {!row.linked_system && row.type === 'K162' && !!k162TypeOption && <>{renderK162Type(k162TypeOption)}</>} | ||||
|         {!row.linked_system && row.type === 'K162' && k162TypeOption && renderK162Type(k162TypeOption)} | ||||
|  | ||||
|         {row.linked_system && ( | ||||
|           <> | ||||
|   | ||||
| @@ -70,6 +70,11 @@ export const SystemStructures: React.FC = () => { | ||||
|           <WdImgButton | ||||
|             className={`${PrimeIcons.CLOCK} text-sky-400 hover:text-sky-200 transition duration-300`} | ||||
|             onClick={handlePasteTimer} | ||||
|             tooltip={{ | ||||
|               position: TooltipPosition.left, | ||||
|               // @ts-ignore | ||||
|               content: 'Add Structures/Timer', | ||||
|             }} | ||||
|           /> | ||||
|           <WdImgButton | ||||
|             className={PrimeIcons.QUESTION_CIRCLE} | ||||
| @@ -79,14 +84,14 @@ export const SystemStructures: React.FC = () => { | ||||
|               content: ( | ||||
|                 <div className="flex flex-col gap-1"> | ||||
|                   <InfoDrawer title={<b className="text-slate-50">How to add/update structures?</b>}> | ||||
|                     In game, select one or more structures in D-Scan and press Ctrl+C, | ||||
|                     In game, select one or more structures in D-Scan and then | ||||
|                     <br /> | ||||
|                     then click on this widget and press Ctrl+V | ||||
|                     use the blue add structure data button | ||||
|                   </InfoDrawer> | ||||
|                   <InfoDrawer title={<b className="text-slate-50">How to add a timer?</b>}> | ||||
|                     In game, select a structure with an active timer, right click to copy, and then use the | ||||
|                     In game, select a structure with an active timer, right click to copy, and then | ||||
|                     <span className="text-blue-500"> blue </span> | ||||
|                     add timer button | ||||
|                     use the blue add structure data button | ||||
|                   </InfoDrawer> | ||||
|                 </div> | ||||
|               ), | ||||
|   | ||||
| @@ -1,18 +1,24 @@ | ||||
|   .TableRowCompact { | ||||
|     height: 8px; | ||||
|     max-height: 8px; | ||||
|     font-size: 12px !important; | ||||
|     line-height: 8px; | ||||
|   } | ||||
|    | ||||
|   .Table { | ||||
|     font-size: 12px; | ||||
|     border-collapse: collapse; | ||||
|   } | ||||
|    | ||||
| .TableRowCompact { | ||||
|   height: 8px; | ||||
|   max-height: 8px; | ||||
|   font-size: 12px !important; | ||||
|   line-height: 8px; | ||||
| } | ||||
|  | ||||
|   .Tooltip { | ||||
|     white-space: pre-line;  // or pre-wrap | ||||
|     line-height: 1.2rem; | ||||
|   } | ||||
|    | ||||
| .Table { | ||||
|   font-size: 12px; | ||||
|   border-collapse: collapse; | ||||
|   table-layout: fixed; | ||||
|   width: 100%; | ||||
| } | ||||
|  | ||||
| .Table .p-datatable-tbody > tr > td { | ||||
|   white-space: nowrap; | ||||
|   text-overflow: ellipsis; | ||||
|   overflow: hidden; | ||||
| } | ||||
|  | ||||
| .Tooltip { | ||||
|   white-space: pre-line; | ||||
|   line-height: 1.2rem; | ||||
| } | ||||
|   | ||||
| @@ -63,6 +63,7 @@ export const SystemStructuresContent: React.FC<SystemStructuresContentProps> = ( | ||||
|             size="small" | ||||
|             sortMode="single" | ||||
|             rowHover | ||||
|             style={{ tableLayout: 'fixed', width: '100%' }} | ||||
|             onRowClick={handleRowClick} | ||||
|             onRowDoubleClick={handleRowDoubleClick} | ||||
|             rowClassName={rowData => { | ||||
| @@ -74,11 +75,56 @@ export const SystemStructuresContent: React.FC<SystemStructuresContentProps> = ( | ||||
|               ); | ||||
|             }} | ||||
|           > | ||||
|             <Column header="Type" body={renderTypeCell} style={{ width: '160px' }} /> | ||||
|             <Column field="name" header="Name" style={{ width: '120px' }} /> | ||||
|             <Column header="Owner" body={renderOwnerCell} style={{ width: '120px' }} /> | ||||
|             <Column field="status" header="Status" style={{ width: '100px' }} /> | ||||
|             <Column header="Timer" body={renderTimerCell} style={{ width: '110px' }} /> | ||||
|             <Column | ||||
|               header="Type" | ||||
|               body={renderTypeCell} | ||||
|               style={{ | ||||
|                 width: '160px', | ||||
|                 whiteSpace: 'nowrap', | ||||
|                 overflow: 'hidden', | ||||
|                 textOverflow: 'ellipsis', | ||||
|               }} | ||||
|             /> | ||||
|             <Column | ||||
|               field="name" | ||||
|               header="Name" | ||||
|               style={{ | ||||
|                 width: '120px', | ||||
|                 whiteSpace: 'nowrap', | ||||
|                 overflow: 'hidden', | ||||
|                 textOverflow: 'ellipsis', | ||||
|               }} | ||||
|             /> | ||||
|             <Column | ||||
|               header="Owner" | ||||
|               body={renderOwnerCell} | ||||
|               style={{ | ||||
|                 width: '120px', | ||||
|                 whiteSpace: 'nowrap', | ||||
|                 overflow: 'hidden', | ||||
|                 textOverflow: 'ellipsis', | ||||
|               }} | ||||
|             /> | ||||
|             <Column | ||||
|               field="status" | ||||
|               header="Status" | ||||
|               style={{ | ||||
|                 width: '100px', | ||||
|                 whiteSpace: 'nowrap', | ||||
|                 overflow: 'hidden', | ||||
|                 textOverflow: 'ellipsis', | ||||
|               }} | ||||
|             /> | ||||
|             <Column | ||||
|               header="Timer" | ||||
|               body={renderTimerCell} | ||||
|               style={{ | ||||
|                 width: '110px', | ||||
|                 whiteSpace: 'nowrap', | ||||
|                 overflow: 'hidden', | ||||
|                 textOverflow: 'ellipsis', | ||||
|               }} | ||||
|             /> | ||||
|             <Column | ||||
|               body={(rowData: StructureItem) => ( | ||||
|                 <i | ||||
| @@ -90,7 +136,13 @@ export const SystemStructuresContent: React.FC<SystemStructuresContentProps> = ( | ||||
|                   }} | ||||
|                 /> | ||||
|               )} | ||||
|               style={{ width: '40px', textAlign: 'center' }} | ||||
|               style={{ | ||||
|                 width: '40px', | ||||
|                 textAlign: 'center', | ||||
|                 whiteSpace: 'nowrap', | ||||
|                 overflow: 'hidden', | ||||
|                 textOverflow: 'ellipsis', | ||||
|               }} | ||||
|             /> | ||||
|           </DataTable> | ||||
|         </div> | ||||
|   | ||||
| @@ -2,6 +2,7 @@ import React, { useEffect, useState, useCallback } from 'react'; | ||||
| import { Dialog } from 'primereact/dialog'; | ||||
| import { Button } from 'primereact/button'; | ||||
| import { AutoComplete } from 'primereact/autocomplete'; | ||||
| import { Calendar } from 'primereact/calendar'; | ||||
| import clsx from 'clsx'; | ||||
|  | ||||
| import { StructureItem, StructureStatus, statusesRequiringTimer, formatToISO } from '../helpers'; | ||||
| @@ -53,7 +54,9 @@ export const SystemStructuresDialog: React.FC<StructuresEditDialogProps> = ({ | ||||
|  | ||||
|       // If user typed more text but we have partial match in prevResults | ||||
|       if (newQuery.startsWith(prevQuery) && prevResults.length > 0) { | ||||
|         const filtered = prevResults.filter(item => item.label.toLowerCase().includes(newQuery.toLowerCase())); | ||||
|         const filtered = prevResults.filter(item => | ||||
|           item.label.toLowerCase().includes(newQuery.toLowerCase()), | ||||
|         ); | ||||
|         setOwnerSuggestions(filtered); | ||||
|         return; | ||||
|       } | ||||
| @@ -74,12 +77,18 @@ export const SystemStructuresDialog: React.FC<StructuresEditDialogProps> = ({ | ||||
|     [prevQuery, prevResults, outCommand], | ||||
|   ); | ||||
|  | ||||
|   const handleChange = (field: keyof StructureItem, val: string) => { | ||||
|   const handleChange = (field: keyof StructureItem, val: string | Date) => { | ||||
|     // If we want to forbid changing structureTypeId or structureType from the dialog, do so here: | ||||
|     if (field === 'structureTypeId' || field === 'structureType') return; | ||||
|  | ||||
|     setEditData(prev => { | ||||
|       if (!prev) return null; | ||||
|  | ||||
|       // If this is the endTime (Date from Calendar), we store as ISO or string: | ||||
|       if (field === 'endTime' && val instanceof Date) { | ||||
|         return { ...prev, endTime: val.toISOString() }; | ||||
|       } | ||||
|  | ||||
|       return { ...prev, [field]: val }; | ||||
|     }); | ||||
|   }; | ||||
| @@ -87,7 +96,9 @@ export const SystemStructuresDialog: React.FC<StructuresEditDialogProps> = ({ | ||||
|   // when user picks a corp from auto-complete | ||||
|   const handleSelectOwner = (selected: { label: string; value: string }) => { | ||||
|     setOwnerInput(selected.label); | ||||
|     setEditData(prev => (prev ? { ...prev, ownerName: selected.label, ownerId: selected.value } : null)); | ||||
|     setEditData(prev => | ||||
|       prev ? { ...prev, ownerName: selected.label, ownerId: selected.value } : null, | ||||
|     ); | ||||
|   }; | ||||
|  | ||||
|   const handleStatusChange = (val: string) => { | ||||
| @@ -107,7 +118,7 @@ export const SystemStructuresDialog: React.FC<StructuresEditDialogProps> = ({ | ||||
|     if (!statusesRequiringTimer.includes(editData.status)) { | ||||
|       editData.endTime = ''; | ||||
|     } else if (editData.endTime) { | ||||
|       // convert to full ISO | ||||
|       // convert to full ISO if not already | ||||
|       editData.endTime = formatToISO(editData.endTime); | ||||
|     } | ||||
|  | ||||
| @@ -146,7 +157,11 @@ export const SystemStructuresDialog: React.FC<StructuresEditDialogProps> = ({ | ||||
|       <div className="flex flex-col gap-2 text-[14px]"> | ||||
|         <label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center"> | ||||
|           <span>Type:</span> | ||||
|           <input readOnly className="p-inputtext p-component cursor-not-allowed" value={editData.structureType ?? ''} /> | ||||
|           <input | ||||
|             readOnly | ||||
|             className="p-inputtext p-component cursor-not-allowed" | ||||
|             value={editData.structureType ?? ''} | ||||
|           /> | ||||
|         </label> | ||||
|         <label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center"> | ||||
|           <span>Name:</span> | ||||
| @@ -186,17 +201,21 @@ export const SystemStructuresDialog: React.FC<StructuresEditDialogProps> = ({ | ||||
|             <option value="Reinforced">Reinforced</option> | ||||
|           </select> | ||||
|         </label> | ||||
|  | ||||
|         {statusesRequiringTimer.includes(editData.status) && ( | ||||
|           <label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center"> | ||||
|             <span>End Time:</span> | ||||
|             <input | ||||
|               type="datetime-local" | ||||
|               className="p-inputtext p-component" | ||||
|               value={editData.endTime ? editData.endTime.replace('Z', '').slice(0, 16) : ''} | ||||
|               onChange={e => handleChange('endTime', e.target.value)} | ||||
|             <span>Timer <br /> (Eve Time):</span> | ||||
|             <Calendar | ||||
|               value={editData.endTime ? new Date(editData.endTime) : undefined} | ||||
|               onChange={(e) => handleChange('endTime', e.value ?? '')} | ||||
|               showTime | ||||
|               hourFormat="24" | ||||
|               dateFormat="yy-mm-dd" | ||||
|               showIcon | ||||
|             /> | ||||
|           </label> | ||||
|         )} | ||||
|  | ||||
|         <label className="grid grid-cols-[100px_1fr] gap-2 items-start mt-2"> | ||||
|           <span className="mt-1">Notes:</span> | ||||
|           <textarea | ||||
|   | ||||
| @@ -2,7 +2,9 @@ import clsx from 'clsx'; | ||||
| import classes from './PassageCard.module.scss'; | ||||
| import { Passage } from '@/hooks/Mapper/types'; | ||||
| import { TimeAgo } from '@/hooks/Mapper/components/ui-kit'; | ||||
| import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper'; | ||||
| import { kgToTons } from '@/hooks/Mapper/utils/kgToTons.ts'; | ||||
| import { useMemo } from 'react'; | ||||
|  | ||||
| type PassageCardType = { | ||||
|   // compact?: boolean; | ||||
| @@ -26,6 +28,11 @@ export const getShipName = (name: string) => { | ||||
| export const PassageCard = ({ inserted_at, character: char, ship }: PassageCardType) => { | ||||
|   const isOwn = false; | ||||
|  | ||||
|   const insertedAt = useMemo(() => { | ||||
|     const date = new Date(inserted_at); | ||||
|     return date.toLocaleString(); | ||||
|   }, [inserted_at]); | ||||
|  | ||||
|   return ( | ||||
|     <div className={clsx(classes.CharacterCard, 'w-full text-xs', 'flex flex-col box-border')}> | ||||
|       <div className="flex flex-col justify-between px-2 py-1 gap-1"> | ||||
| @@ -76,7 +83,9 @@ export const PassageCard = ({ inserted_at, character: char, ship }: PassageCardT | ||||
|             {/*time and class*/} | ||||
|             <div className="flex justify-between"> | ||||
|               <span className="text-stone-400"> | ||||
|                 <TimeAgo timestamp={inserted_at} /> | ||||
|                 <WdTooltipWrapper content={insertedAt}> | ||||
|                   <TimeAgo timestamp={inserted_at} /> | ||||
|                 </WdTooltipWrapper> | ||||
|               </span> | ||||
|  | ||||
|               <div className="text-stone-400">{kgToTons(parseInt(ship.ship_type_info.mass))}</div> | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import styles from './MapSettings.module.scss'; | ||||
|  | ||||
| import { WdCheckbox } from '@/hooks/Mapper/components/ui-kit'; | ||||
|  | ||||
| interface PrettySwitchboxProps { | ||||
|   | ||||
| @@ -53,6 +53,7 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map | ||||
|             ...out, | ||||
|             custom_info: JSON.stringify({ | ||||
|               k162Type: values.k162Type, | ||||
|               isEOL: values.isEOL, | ||||
|             }), | ||||
|           }; | ||||
|  | ||||
| @@ -127,14 +128,17 @@ export const SignatureSettings = ({ systemId, show, onHide, signatureData }: Map | ||||
|     const { linked_system, custom_info, ...rest } = signatureData; | ||||
|  | ||||
|     let k162Type = null; | ||||
|     let isEOL = false; | ||||
|     if (custom_info) { | ||||
|       const customInfo = JSON.parse(custom_info); | ||||
|       k162Type = customInfo.k162Type; | ||||
|       isEOL = customInfo.isEOL; | ||||
|     } | ||||
|  | ||||
|     signatureForm.reset({ | ||||
|       linked_system: linked_system?.solar_system_id.toString() ?? undefined, | ||||
|       k162Type: k162Type, | ||||
|       isEOL: isEOL, | ||||
|       ...rest, | ||||
|     }); | ||||
|   }, [signatureForm, signatureData]); | ||||
|   | ||||
| @@ -0,0 +1,24 @@ | ||||
| import { InputSwitch } from 'primereact/inputswitch'; | ||||
| import { Controller, useFormContext } from 'react-hook-form'; | ||||
| import { SystemSignature } from '@/hooks/Mapper/types'; | ||||
|  | ||||
| export interface SignatureEOLCheckboxProps { | ||||
|   name: string; | ||||
|   defaultValue?: boolean; | ||||
| } | ||||
|  | ||||
| export const SignatureEOLCheckbox = ({ name, defaultValue = false }: SignatureEOLCheckboxProps) => { | ||||
|   const { control } = useFormContext<SystemSignature>(); | ||||
|  | ||||
|   return ( | ||||
|     <Controller | ||||
|       // @ts-ignore | ||||
|       name={name} | ||||
|       control={control} | ||||
|       defaultValue={defaultValue} | ||||
|       render={({ field }) => { | ||||
|         return <InputSwitch className="my-1" checked={!!field.value} onChange={e => field.onChange(e.value)} />; | ||||
|       }} | ||||
|     /> | ||||
|   ); | ||||
| }; | ||||
| @@ -0,0 +1 @@ | ||||
| export * from './SignatureEOLCheckbox.tsx'; | ||||
| @@ -3,6 +3,7 @@ import { SystemSignature } from '@/hooks/Mapper/types'; | ||||
| 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 { SignatureEOLCheckbox } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings/components/SignatureEOLCheckbox'; | ||||
|  | ||||
| export const SignatureGroupContentWormholes = () => { | ||||
|   const { watch } = useFormContext<SystemSignature>(); | ||||
| @@ -26,6 +27,11 @@ export const SignatureGroupContentWormholes = () => { | ||||
|         <span>Leads To:</span> | ||||
|         <SignatureLeadsToSelect name="linked_system" /> | ||||
|       </label> | ||||
|  | ||||
|       <label className="grid grid-cols-[100px_250px_1fr] gap-2 items-center text-[14px]"> | ||||
|         <span>EOL:</span> | ||||
|         <SignatureEOLCheckbox name="isEOL" /> | ||||
|       </label> | ||||
|     </> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -3,100 +3,8 @@ 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 | ||||
|     /> | ||||
|   ); | ||||
| }; | ||||
| import { K162_TYPES } from '@/hooks/Mapper/constants.ts'; | ||||
| import { renderK162Type } from '.'; | ||||
|  | ||||
| export interface SignatureK162TypeSelectProps { | ||||
|   name: string; | ||||
| @@ -107,7 +15,7 @@ export const SignatureK162TypeSelect = ({ name, defaultValue = '' }: SignatureK1 | ||||
|   const { control } = useFormContext<SystemSignature>(); | ||||
|  | ||||
|   const options = useMemo(() => { | ||||
|     return [{ value: null }, ...k162Types]; | ||||
|     return [{ value: null }, ...K162_TYPES]; | ||||
|   }, []); | ||||
|  | ||||
|   return ( | ||||
|   | ||||
| @@ -1 +1,2 @@ | ||||
| export * from './SignatureK162TypeSelect.tsx'; | ||||
| export * from './renderK162Type.tsx'; | ||||
|   | ||||
| @@ -0,0 +1,26 @@ | ||||
| import { WHClassView } from '@/hooks/Mapper/components/ui-kit'; | ||||
| import { K162Type } from '@/hooks/Mapper/constants.ts'; | ||||
|  | ||||
| const renderNoValue = () => <div className="flex gap-2 items-center">-Unknown-</div>; | ||||
|  | ||||
| export const renderK162Type = (option: K162Type) => { | ||||
|   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 | ||||
|     /> | ||||
|   ); | ||||
| }; | ||||
| @@ -14,9 +14,10 @@ import classes from './MapWrapper.module.scss'; | ||||
| import { Connections } from '@/hooks/Mapper/components/mapRootContent/components/Connections'; | ||||
| import { ContextMenuSystemMultiple, useContextMenuSystemMultipleHandlers } from '../contexts/ContextMenuSystemMultiple'; | ||||
| import { getSystemById } from '@/hooks/Mapper/helpers'; | ||||
| import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts'; | ||||
| import { Node, XYPosition } from 'reactflow'; | ||||
|  | ||||
| import { Commands } from '@/hooks/Mapper/types/mapHandlers.ts'; | ||||
| import { useCommandsSystems } from '@/hooks/Mapper/mapRootProvider/hooks/api'; | ||||
| import { emitMapEvent, useMapEventListener } from '@/hooks/Mapper/events'; | ||||
|  | ||||
| import { STORED_INTERFACE_DEFAULT_VALUES } from '@/hooks/Mapper/mapRootProvider/MapRootProvider'; | ||||
| @@ -32,7 +33,7 @@ export const MapWrapper = () => { | ||||
|   const { | ||||
|     update, | ||||
|     outCommand, | ||||
|     data: { selectedConnections, selectedSystems, hubs, systems }, | ||||
|     data: { selectedConnections, selectedSystems, hubs, systems, connections, linkSignatureToSystem }, | ||||
|     interfaceSettings: { | ||||
|       isShowMenu, | ||||
|       isShowMinimap = STORED_INTERFACE_DEFAULT_VALUES.isShowMinimap, | ||||
| @@ -46,25 +47,19 @@ export const MapWrapper = () => { | ||||
|   const { deleteSystems } = useDeleteSystems(); | ||||
|   const { mapRef, runCommand } = useCommonMapEventProcessor(); | ||||
|  | ||||
|   const { updateLinkSignatureToSystem } = useCommandsSystems(); | ||||
|   const { open, ...systemContextProps } = useContextMenuSystemHandlers({ systems, hubs, outCommand }); | ||||
|   const { handleSystemMultipleContext, ...systemMultipleCtxProps } = useContextMenuSystemMultipleHandlers(); | ||||
|  | ||||
|   const [openSettings, setOpenSettings] = useState<string | null>(null); | ||||
|   const [openLinkSignatures, setOpenLinkSignatures] = useState<any | null>(null); | ||||
|   const [openCustomLabel, setOpenCustomLabel] = useState<string | null>(null); | ||||
|   const [openAddSystem, setOpenAddSystem] = useState<XYPosition | null>(null); | ||||
|   const [selectedConnection, setSelectedConnection] = useState<SolarSystemConnection | null>(null); | ||||
|  | ||||
|   const ref = useRef({ selectedConnections, selectedSystems, systemContextProps, systems, deleteSystems }); | ||||
|   ref.current = { selectedConnections, selectedSystems, systemContextProps, systems, deleteSystems }; | ||||
|   const ref = useRef({ selectedConnections, selectedSystems, systemContextProps, systems, connections, deleteSystems }); | ||||
|   ref.current = { selectedConnections, selectedSystems, systemContextProps, systems, connections, deleteSystems }; | ||||
|  | ||||
|   useMapEventListener(event => { | ||||
|     switch (event.name) { | ||||
|       case Commands.linkSignatureToSystem: | ||||
|         setOpenLinkSignatures(event.data); | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     runCommand(event); | ||||
|   }); | ||||
|  | ||||
| @@ -130,6 +125,11 @@ export const MapWrapper = () => { | ||||
|     setOpenAddSystem(coordinates); | ||||
|   }, []); | ||||
|  | ||||
|   const canRemoveConnection = useCallback((connectionId: string) => { | ||||
|     const { connections } = ref.current; | ||||
|     return !connections.some(x => x.id === connectionId); | ||||
|   }, []); | ||||
|  | ||||
|   const handleSubmitAddSystem: SearchOnSubmitCallback = useCallback( | ||||
|     async item => { | ||||
|       if (ref.current.systems.some(x => x.system_static_info.solar_system_id === item.value)) { | ||||
| @@ -166,6 +166,7 @@ export const MapWrapper = () => { | ||||
|         isSoftBackground={isSoftBackground} | ||||
|         theme={theme} | ||||
|         onAddSystem={onAddSystem} | ||||
|         canRemoveConnection={canRemoveConnection} | ||||
|       /> | ||||
|  | ||||
|       {openSettings != null && ( | ||||
| @@ -176,8 +177,8 @@ export const MapWrapper = () => { | ||||
|         <SystemCustomLabelDialog systemId={openCustomLabel} visible setVisible={() => setOpenCustomLabel(null)} /> | ||||
|       )} | ||||
|  | ||||
|       {openLinkSignatures != null && ( | ||||
|         <SystemLinkSignatureDialog data={openLinkSignatures} setVisible={() => setOpenLinkSignatures(null)} /> | ||||
|       {linkSignatureToSystem != null && ( | ||||
|         <SystemLinkSignatureDialog data={linkSignatureToSystem} setVisible={() => updateLinkSignatureToSystem(null)} /> | ||||
|       )} | ||||
|  | ||||
|       <AddSystemDialog | ||||
|   | ||||
| @@ -384,6 +384,10 @@ export const WindowManager: React.FC<WindowManagerProps> = ({ windows: initialWi | ||||
|             next.position.y = container.clientHeight - next.size.height - SNAP_GAP; | ||||
|           } | ||||
|  | ||||
|           if (next.position.y < 0) { | ||||
|             next.position.y = 0; | ||||
|           } | ||||
|  | ||||
|           return next; | ||||
|         }); | ||||
|       }); | ||||
|   | ||||
| @@ -65,3 +65,77 @@ export const REGIONS_MAP: Record<number, Spaces> = { | ||||
|   [Regions.TashMurkon]: Spaces.Amarr, | ||||
|   [Regions.VergeVendor]: Spaces.Gallente, | ||||
| }; | ||||
|  | ||||
| export type K162Type = { | ||||
|   label: string; | ||||
|   value: string; | ||||
|   whClassName: string; | ||||
| }; | ||||
|  | ||||
| export const K162_TYPES: K162Type[] = [ | ||||
|   { | ||||
|     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', | ||||
|   }, | ||||
| ]; | ||||
|  | ||||
| export const K162_TYPES_MAP: { [key: string]: K162Type } = K162_TYPES.reduce( | ||||
|   (acc, x) => ({ ...acc, [x.value]: x }), | ||||
|   {}, | ||||
| ); | ||||
|   | ||||
| @@ -0,0 +1,9 @@ | ||||
| import { SignatureCustomInfo } from '@/hooks/Mapper/types'; | ||||
|  | ||||
| export const parseSignatureCustomInfo = (str: string | undefined): SignatureCustomInfo => { | ||||
|   if (str == null || str === '') { | ||||
|     return {}; | ||||
|   } | ||||
|  | ||||
|   return JSON.parse(str); | ||||
| }; | ||||
| @@ -10,10 +10,12 @@ import { | ||||
|   useStoreWidgets, | ||||
|   WindowStoreInfo, | ||||
| } from '@/hooks/Mapper/mapRootProvider/hooks/useStoreWidgets.ts'; | ||||
| import { CommandLinkSignatureToSystem } from '@/hooks/Mapper/types'; | ||||
|  | ||||
| export type MapRootData = MapUnionTypes & { | ||||
|   selectedSystems: string[]; | ||||
|   selectedConnections: Pick<SolarSystemConnection, 'source' | 'target'>[]; | ||||
|   linkSignatureToSystem: CommandLinkSignatureToSystem | null; | ||||
| }; | ||||
|  | ||||
| const INITIAL_DATA: MapRootData = { | ||||
| @@ -34,6 +36,7 @@ const INITIAL_DATA: MapRootData = { | ||||
|   selectedConnections: [], | ||||
|   userPermissions: {}, | ||||
|   options: {}, | ||||
|   linkSignatureToSystem: null, | ||||
| }; | ||||
|  | ||||
| export enum InterfaceStoredSettingsProps { | ||||
|   | ||||
| @@ -1,6 +1,11 @@ | ||||
| import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; | ||||
| import { useCallback, useRef } from 'react'; | ||||
| import { CommandAddSystems, CommandRemoveSystems, CommandUpdateSystems } from '@/hooks/Mapper/types'; | ||||
| import { | ||||
|   CommandAddSystems, | ||||
|   CommandRemoveSystems, | ||||
|   CommandUpdateSystems, | ||||
|   CommandLinkSignatureToSystem, | ||||
| } 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'; | ||||
| @@ -74,5 +79,10 @@ export const useCommandsSystems = () => { | ||||
|     [outCommand], | ||||
|   ); | ||||
|  | ||||
|   return { addSystems, removeSystems, updateSystems, updateSystemSignatures }; | ||||
|   const updateLinkSignatureToSystem = useCallback(async (command: CommandLinkSignatureToSystem) => { | ||||
|     const { update } = ref.current; | ||||
|     update({ linkSignatureToSystem: command }, true); | ||||
|   }, []); | ||||
|  | ||||
|   return { addSystems, removeSystems, updateSystems, updateSystemSignatures, updateLinkSignatureToSystem }; | ||||
| }; | ||||
|   | ||||
| @@ -32,7 +32,8 @@ import { emitMapEvent } from '@/hooks/Mapper/events'; | ||||
|  | ||||
| export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => { | ||||
|   const mapInit = useMapInit(); | ||||
|   const { addSystems, removeSystems, updateSystems, updateSystemSignatures } = useCommandsSystems(); | ||||
|   const { addSystems, removeSystems, updateSystems, updateSystemSignatures, updateLinkSignatureToSystem } = | ||||
|     useCommandsSystems(); | ||||
|   const { addConnections, removeConnections, updateConnection } = useCommandsConnections(); | ||||
|   const { charactersUpdated, characterAdded, characterRemoved, characterUpdated, presentCharacters } = | ||||
|     useCommandsCharacters(); | ||||
| @@ -93,7 +94,9 @@ export const useMapRootHandlers = (ref: ForwardedRef<MapHandlers>) => { | ||||
|               break; | ||||
|  | ||||
|             case Commands.linkSignatureToSystem: // USED | ||||
|               // do nothing here | ||||
|               setTimeout(() => { | ||||
|                 updateLinkSignatureToSystem(data as CommandLinkSignatureToSystem); | ||||
|               }, 200); | ||||
|               break; | ||||
|  | ||||
|             case Commands.centerSystem: // USED | ||||
|   | ||||
| @@ -26,15 +26,20 @@ export type GroupType = { | ||||
|   h: number; | ||||
| }; | ||||
|  | ||||
| export type SignatureCustomInfo = { | ||||
|   k162Type?: string; | ||||
|   isEOL?: boolean; | ||||
| }; | ||||
|  | ||||
| export type SystemSignature = { | ||||
|   eve_id: string; | ||||
|   kind: SignatureKind; | ||||
|   name: string; | ||||
|   // SignatureCustomInfo | ||||
|   custom_info?: string; | ||||
|   description?: string; | ||||
|   group: SignatureGroup; | ||||
|   type: string; | ||||
|   k162Type?: string; | ||||
|   linked_system?: SolarSystemStaticInfoRaw; | ||||
|   inserted_at?: string; | ||||
|   updated_at?: string; | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								assets/static/images/news/01-20-structure-widget/cover.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/static/images/news/01-20-structure-widget/cover.png
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 23 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/static/images/news/01-20-structure-widget/enable-widget.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/static/images/news/01-20-structure-widget/enable-widget.png
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 34 KiB | 
| @@ -64,7 +64,19 @@ map_subscription_characters_limit = | ||||
|  | ||||
| map_subscription_hubs_limit = | ||||
|   config_dir | ||||
|   |> get_int_from_path_or_env("WANDERER_MAP_SUBSCRIPTION_HUBS_LIMIT", 100) | ||||
|   |> get_int_from_path_or_env("WANDERER_MAP_SUBSCRIPTION_HUBS_LIMIT", 10) | ||||
|  | ||||
| map_subscription_base_price = | ||||
|   config_dir | ||||
|   |> get_int_from_path_or_env("WANDERER_MAP_SUBSCRIPTION_BASE_PRICE", 100_000_000) | ||||
|  | ||||
| map_subscription_extra_characters_100_price = | ||||
|   config_dir | ||||
|   |> get_int_from_path_or_env("WANDERER_MAP_SUBSCRIPTION_EXTRA_CHARACTERS_100_PRICE", 50_000_000) | ||||
|  | ||||
| map_subscription_extra_hubs_10_price = | ||||
|   config_dir | ||||
|   |> get_int_from_path_or_env("WANDERER_MAP_SUBSCRIPTION_EXTRA_HUBS_10_PRICE", 10_000_000) | ||||
|  | ||||
| map_connection_auto_expire_hours = | ||||
|   config_dir | ||||
| @@ -76,7 +88,7 @@ map_connection_auto_eol_hours = | ||||
|  | ||||
| map_connection_eol_expire_timeout_mins = | ||||
|   config_dir | ||||
|   |> get_int_from_path_or_env("WANDERER_MAP_CONNECTION_EOL_EXPIRE_TIMEOUT_MINS", 30) | ||||
|   |> get_int_from_path_or_env("WANDERER_MAP_CONNECTION_EOL_EXPIRE_TIMEOUT_MINS", 60) | ||||
|  | ||||
| wallet_tracking_enabled = | ||||
|   config_dir | ||||
| @@ -117,16 +129,16 @@ config :wanderer_app, | ||||
|       }, | ||||
|       %{ | ||||
|         id: "omega", | ||||
|         characters_limit: 300, | ||||
|         hubs_limit: 20, | ||||
|         base_price: 250_000_000, | ||||
|         characters_limit: map_subscription_characters_limit * 2, | ||||
|         hubs_limit: map_subscription_hubs_limit * 2, | ||||
|         base_price: map_subscription_base_price, | ||||
|         month_3_discount: 0.2, | ||||
|         month_6_discount: 0.4, | ||||
|         month_12_discount: 0.5 | ||||
|       } | ||||
|     ], | ||||
|     extra_characters_100: 75_000_000, | ||||
|     extra_hubs_10: 25_000_000 | ||||
|     extra_characters_100: map_subscription_extra_characters_100_price, | ||||
|     extra_hubs_10: map_subscription_extra_hubs_10_price | ||||
|   } | ||||
|  | ||||
| config :ueberauth, Ueberauth, | ||||
|   | ||||
| @@ -95,7 +95,9 @@ defmodule WandererApp.Api.UserActivity do | ||||
|           :map_acl_member_updated, | ||||
|           :map_connection_added, | ||||
|           :map_connection_updated, | ||||
|           :map_connection_removed | ||||
|           :map_connection_removed, | ||||
|           :signatures_added, | ||||
|           :signatures_removed | ||||
|         ] | ||||
|       ) | ||||
|  | ||||
| @@ -108,8 +110,6 @@ defmodule WandererApp.Api.UserActivity do | ||||
|     update_timestamp(:updated_at) | ||||
|   end | ||||
|  | ||||
|  | ||||
|  | ||||
|   relationships do | ||||
|     belongs_to :character, WandererApp.Api.Character do | ||||
|       allow_nil? true | ||||
|   | ||||
| @@ -68,7 +68,7 @@ defmodule WandererApp.Map do | ||||
|   end | ||||
|  | ||||
|   def get_characters_limit(map_id), | ||||
|     do: {:ok, map_id |> get_map!() |> Map.get(:characters_limit, 100)} | ||||
|     do: {:ok, map_id |> get_map!() |> Map.get(:characters_limit, 50)} | ||||
|  | ||||
|   def is_subscription_active?(map_id) do | ||||
|     {:ok, %{plan: plan}} = WandererApp.Map.SubscriptionManager.get_active_map_subscription(map_id) | ||||
|   | ||||
| @@ -206,8 +206,24 @@ defmodule WandererApp.Map.Server.SystemsImpl do | ||||
|         user_id, | ||||
|         character_id | ||||
|       ) do | ||||
|     connections_to_remove = | ||||
|     filtered_ids = | ||||
|       removed_ids | ||||
|       |> Enum.map(fn solar_system_id -> | ||||
|         WandererApp.Map.find_system_by_location(map_id, %{solar_system_id: solar_system_id}) | ||||
|       end) | ||||
|       |> Enum.filter(fn system -> not is_nil(system) && not system.locked end) | ||||
|       |> Enum.map(&{&1.solar_system_id, &1.id}) | ||||
|  | ||||
|     solar_system_ids_to_remove = | ||||
|       filtered_ids | ||||
|       |> Enum.map(fn {solar_system_id, _} -> solar_system_id end) | ||||
|  | ||||
|     system_ids_to_remove = | ||||
|       filtered_ids | ||||
|       |> Enum.map(fn {_, system_id} -> system_id end) | ||||
|  | ||||
|     connections_to_remove = | ||||
|       solar_system_ids_to_remove | ||||
|       |> Enum.map(fn solar_system_id -> | ||||
|         WandererApp.Map.find_connections(map_id, solar_system_id) | ||||
|       end) | ||||
| @@ -215,9 +231,9 @@ defmodule WandererApp.Map.Server.SystemsImpl do | ||||
|       |> Enum.uniq_by(& &1.id) | ||||
|  | ||||
|     :ok = WandererApp.Map.remove_connections(map_id, connections_to_remove) | ||||
|     :ok = WandererApp.Map.remove_systems(map_id, removed_ids) | ||||
|     :ok = WandererApp.Map.remove_systems(map_id, solar_system_ids_to_remove) | ||||
|  | ||||
|     removed_ids | ||||
|     solar_system_ids_to_remove | ||||
|     |> Enum.each(fn solar_system_id -> | ||||
|       map_id | ||||
|       |> WandererApp.MapSystemRepo.remove_from_map(solar_system_id) | ||||
| @@ -237,7 +253,7 @@ defmodule WandererApp.Map.Server.SystemsImpl do | ||||
|       WandererApp.MapConnectionRepo.destroy(map_id, connection) | ||||
|     end) | ||||
|  | ||||
|     removed_ids | ||||
|     solar_system_ids_to_remove | ||||
|     |> Enum.map(fn solar_system_id -> | ||||
|       WandererApp.Api.MapSystemSignature.by_linked_system_id!(solar_system_id) | ||||
|     end) | ||||
| @@ -250,10 +266,28 @@ defmodule WandererApp.Map.Server.SystemsImpl do | ||||
|       Impl.broadcast!(map_id, :signatures_updated, system.solar_system_id) | ||||
|     end) | ||||
|  | ||||
|     @ddrt.delete(removed_ids, rtree_name) | ||||
|     linked_system_ids = | ||||
|       system_ids_to_remove | ||||
|       |> Enum.map(fn system_id -> | ||||
|         WandererApp.Api.MapSystemSignature.by_system_id!(system_id) | ||||
|         |> Enum.filter(fn s -> not is_nil(s.linked_system_id) end) | ||||
|         |> Enum.map(fn s -> s.linked_system_id end) | ||||
|       end) | ||||
|       |> List.flatten() | ||||
|       |> Enum.uniq() | ||||
|  | ||||
|     linked_system_ids | ||||
|     |> Enum.each(fn linked_system_id -> | ||||
|       WandererApp.Map.Server.update_system_linked_sig_eve_id(map_id, %{ | ||||
|         solar_system_id: linked_system_id, | ||||
|         linked_sig_eve_id: nil | ||||
|       }) | ||||
|     end) | ||||
|  | ||||
|     @ddrt.delete(solar_system_ids_to_remove, rtree_name) | ||||
|  | ||||
|     Impl.broadcast!(map_id, :remove_connections, connections_to_remove) | ||||
|     Impl.broadcast!(map_id, :systems_removed, removed_ids) | ||||
|     Impl.broadcast!(map_id, :systems_removed, solar_system_ids_to_remove) | ||||
|  | ||||
|     case not is_nil(user_id) do | ||||
|       true -> | ||||
| @@ -262,12 +296,12 @@ defmodule WandererApp.Map.Server.SystemsImpl do | ||||
|             character_id: character_id, | ||||
|             user_id: user_id, | ||||
|             map_id: map_id, | ||||
|             solar_system_ids: removed_ids | ||||
|             solar_system_ids: solar_system_ids_to_remove | ||||
|           }) | ||||
|  | ||||
|         :telemetry.execute( | ||||
|           [:wanderer_app, :map, :systems, :remove], | ||||
|           %{count: removed_ids |> Enum.count()} | ||||
|           %{count: solar_system_ids_to_remove |> Enum.count()} | ||||
|         ) | ||||
|  | ||||
|         :ok | ||||
|   | ||||
| @@ -55,18 +55,33 @@ defmodule WandererApp.Maps do | ||||
|  | ||||
|   def get_available_maps(current_user) do | ||||
|     case WandererApp.Api.Map.available(%{}, actor: current_user) do | ||||
|       {:ok, maps} -> {:ok, maps |> _filter_blocked_maps(current_user)} | ||||
|       {:ok, maps} -> {:ok, maps |> filter_blocked_maps(current_user)} | ||||
|       _ -> {:ok, []} | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def get_tracked_map_characters(map_id, current_user) do | ||||
|     case WandererApp.MapCharacterSettingsRepo.get_tracked_by_map_filtered( | ||||
|            map_id, | ||||
|            current_user.characters |> Enum.map(& &1.id) | ||||
|          ) do | ||||
|       {:ok, settings} -> | ||||
|         {:ok, | ||||
|          settings | ||||
|          |> Enum.map(fn s -> s |> Ash.load!(:character) |> Map.get(:character) end)} | ||||
|  | ||||
|       _ -> | ||||
|         {:ok, []} | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def load_characters(map, character_settings, user_id) do | ||||
|     {:ok, user_characters} = | ||||
|       WandererApp.Api.Character.active_by_user(%{user_id: user_id}) | ||||
|  | ||||
|     characters = | ||||
|       map | ||||
|       |> _get_map_available_characters(user_characters) | ||||
|       |> get_map_available_characters(user_characters) | ||||
|       |> Enum.map(fn c -> | ||||
|         map_character(c, character_settings |> Enum.find(&(&1.character_id == c.id))) | ||||
|       end) | ||||
| @@ -146,7 +161,7 @@ defmodule WandererApp.Maps do | ||||
|      }} | ||||
|   end | ||||
|  | ||||
|   defp _get_map_available_characters(map, user_characters) do | ||||
|   defp get_map_available_characters(map, user_characters) do | ||||
|     {:ok, | ||||
|      %{ | ||||
|        map_acl_owner_ids: map_acl_owner_ids, | ||||
| @@ -164,7 +179,7 @@ defmodule WandererApp.Maps do | ||||
|     end) | ||||
|   end | ||||
|  | ||||
|   defp _filter_blocked_maps(maps, current_user) do | ||||
|   defp filter_blocked_maps(maps, current_user) do | ||||
|     user_character_ids = current_user.characters |> Enum.map(& &1.id) | ||||
|     user_character_eve_ids = current_user.characters |> Enum.map(& &1.eve_id) | ||||
|  | ||||
|   | ||||
| @@ -74,7 +74,7 @@ defmodule WandererAppWeb.UserActivity do | ||||
|       </p> | ||||
|  | ||||
|       <p class="text-sm text-[var(--color-gray-4)] w-[15%]"> | ||||
|         <%= _get_event_name(@activity.event_type) %> | ||||
|         <%= get_event_name(@activity.event_type) %> | ||||
|       </p> | ||||
|       <.activity_event event_type={@activity.event_type} event_data={@activity.event_data} /> | ||||
|  | ||||
| @@ -115,7 +115,7 @@ defmodule WandererAppWeb.UserActivity do | ||||
|     <div class="w-[40%]"> | ||||
|       <div class="flex items-center gap-1"> | ||||
|         <h6 class="text-base leading-[150%] font-semibold dark:text-white"> | ||||
|           <%= _get_event_data(@event_type, Jason.decode!(@event_data) |> Map.drop(["character_id"])) %> | ||||
|           <%= get_event_data(@event_type, Jason.decode!(@event_data) |> Map.drop(["character_id"])) %> | ||||
|         </h6> | ||||
|       </div> | ||||
|     </div> | ||||
| @@ -129,107 +129,128 @@ defmodule WandererAppWeb.UserActivity do | ||||
|     {:noreply, socket} | ||||
|   end | ||||
|  | ||||
|   defp _get_event_name(:hub_added), do: "Hub Added" | ||||
|   defp _get_event_name(:hub_removed), do: "Hub Removed" | ||||
|   defp _get_event_name(:map_connection_added), do: "Connection Added" | ||||
|   defp _get_event_name(:map_connection_updated), do: "Connection Updated" | ||||
|   defp _get_event_name(:map_connection_removed), do: "Connection Removed" | ||||
|   defp _get_event_name(:map_acl_added), do: "Acl Added" | ||||
|   defp _get_event_name(:map_acl_removed), do: "Acl Removed" | ||||
|   defp _get_event_name(:system_added), do: "System Added" | ||||
|   defp _get_event_name(:system_updated), do: "System Updated" | ||||
|   defp _get_event_name(:systems_removed), do: "System(s) Removed" | ||||
|   defp _get_event_name(name), do: name | ||||
|   defp get_event_name(:hub_added), do: "Hub Added" | ||||
|   defp get_event_name(:hub_removed), do: "Hub Removed" | ||||
|   defp get_event_name(:map_connection_added), do: "Connection Added" | ||||
|   defp get_event_name(:map_connection_updated), do: "Connection Updated" | ||||
|   defp get_event_name(:map_connection_removed), do: "Connection Removed" | ||||
|   defp get_event_name(:map_acl_added), do: "Acl Added" | ||||
|   defp get_event_name(:map_acl_removed), do: "Acl Removed" | ||||
|   defp get_event_name(:system_added), do: "System Added" | ||||
|   defp get_event_name(:system_updated), do: "System Updated" | ||||
|   defp get_event_name(:systems_removed), do: "System(s) Removed" | ||||
|   defp get_event_name(:signatures_added), do: "Signatures Added" | ||||
|   defp get_event_name(:signatures_removed), do: "Signatures Removed" | ||||
|   defp get_event_name(name), do: name | ||||
|  | ||||
|   # defp _get_event_data(:hub_added, data), do: Jason.encode!(data) | ||||
|   # defp _get_event_data(:hub_removed, data), do: data | ||||
|   defp get_event_data(:map_acl_added, %{"acl_id" => acl_id}) do | ||||
|     {:ok, acl} = WandererApp.AccessListRepo.get(acl_id) | ||||
|     "#{acl.name}" | ||||
|   end | ||||
|  | ||||
|   # defp _get_event_data(:map_acl_added, data), do: data | ||||
|   # defp _get_event_data(:map_acl_removed, data), do: data | ||||
|   # defp _get_event_data(:system_added, data), do: data | ||||
|   defp get_event_data(:map_acl_removed, %{"acl_id" => acl_id}) do | ||||
|     {:ok, acl} = WandererApp.AccessListRepo.get(acl_id) | ||||
|     "#{acl.name}" | ||||
|   end | ||||
|  | ||||
|   # defp get_event_data(:map_acl_removed, data), do: data | ||||
|   # defp get_event_data(:system_added, data), do: data | ||||
|   # | ||||
|  | ||||
|   defp _get_event_data(:system_updated, %{ | ||||
|   defp get_event_data(:system_updated, %{ | ||||
|          "key" => "labels", | ||||
|          "solar_system_id" => solar_system_id, | ||||
|          "value" => value | ||||
|        }) do | ||||
|     system_name = _get_system_name(solar_system_id) | ||||
|     system_name = get_system_name(solar_system_id) | ||||
|  | ||||
|     try do | ||||
|       %{"customLabel" => customLabel, "labels" => labels} = Jason.decode!(value) | ||||
|  | ||||
|       "#{system_name} labels - #{inspect(labels)}, customLabel - #{customLabel}" | ||||
|       "#{system_name}: labels - #{inspect(labels)}, customLabel - #{customLabel}" | ||||
|     rescue | ||||
|       _ -> | ||||
|         "#{system_name} labels - #{inspect(value)}" | ||||
|         "#{system_name}: labels - #{inspect(value)}" | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   defp _get_event_data(:system_added, %{ | ||||
|   defp get_event_data(:system_added, %{ | ||||
|          "solar_system_id" => solar_system_id | ||||
|        }), | ||||
|        do: _get_system_name(solar_system_id) | ||||
|        do: get_system_name(solar_system_id) | ||||
|  | ||||
|   defp _get_event_data(:hub_added, %{ | ||||
|   defp get_event_data(:hub_added, %{ | ||||
|          "solar_system_id" => solar_system_id | ||||
|        }), | ||||
|        do: _get_system_name(solar_system_id) | ||||
|        do: get_system_name(solar_system_id) | ||||
|  | ||||
|   defp _get_event_data(:hub_removed, %{ | ||||
|   defp get_event_data(:hub_removed, %{ | ||||
|          "solar_system_id" => solar_system_id | ||||
|        }), | ||||
|        do: _get_system_name(solar_system_id) | ||||
|        do: get_system_name(solar_system_id) | ||||
|  | ||||
|   defp _get_event_data(:system_updated, %{ | ||||
|   defp get_event_data(:system_updated, %{ | ||||
|          "key" => key, | ||||
|          "solar_system_id" => solar_system_id, | ||||
|          "value" => value | ||||
|        }) do | ||||
|     system_name = _get_system_name(solar_system_id) | ||||
|     "#{system_name} #{key} - #{inspect(value)}" | ||||
|     system_name = get_system_name(solar_system_id) | ||||
|     "#{system_name}: #{key} - #{inspect(value)}" | ||||
|   end | ||||
|  | ||||
|   defp _get_event_data(:systems_removed, %{ | ||||
|   defp get_event_data(:systems_removed, %{ | ||||
|          "solar_system_ids" => solar_system_ids | ||||
|        }), | ||||
|        do: | ||||
|          solar_system_ids | ||||
|          |> Enum.map(&_get_system_name/1) | ||||
|          |> Enum.map(&get_system_name/1) | ||||
|          |> Enum.join(", ") | ||||
|  | ||||
|   defp _get_event_data(:map_connection_added, %{ | ||||
|   defp get_event_data(signatures_event, %{ | ||||
|          "solar_system_id" => solar_system_id, | ||||
|          "signatures" => signatures | ||||
|        }) | ||||
|        when signatures_event in [:signatures_added, :signatures_removed], | ||||
|        do: "#{get_system_name(solar_system_id)}: #{signatures |> Enum.join(", ")}" | ||||
|  | ||||
|   defp get_event_data(signatures_event, %{ | ||||
|          "signatures" => signatures | ||||
|        }) | ||||
|        when signatures_event in [:signatures_added, :signatures_removed], | ||||
|        do: signatures |> Enum.join(", ") | ||||
|  | ||||
|   defp get_event_data(:map_connection_added, %{ | ||||
|          "solar_system_source_id" => solar_system_source_id, | ||||
|          "solar_system_target_id" => solar_system_target_id | ||||
|        }) do | ||||
|     source_system_name = _get_system_name(solar_system_source_id) | ||||
|     target_system_name = _get_system_name(solar_system_target_id) | ||||
|     source_system_name = get_system_name(solar_system_source_id) | ||||
|     target_system_name = get_system_name(solar_system_target_id) | ||||
|     "[#{source_system_name}:#{target_system_name}]" | ||||
|   end | ||||
|  | ||||
|   defp _get_event_data(:map_connection_removed, %{ | ||||
|   defp get_event_data(:map_connection_removed, %{ | ||||
|          "solar_system_source_id" => solar_system_source_id, | ||||
|          "solar_system_target_id" => solar_system_target_id | ||||
|        }) do | ||||
|     source_system_name = _get_system_name(solar_system_source_id) | ||||
|     target_system_name = _get_system_name(solar_system_target_id) | ||||
|     source_system_name = get_system_name(solar_system_source_id) | ||||
|     target_system_name = get_system_name(solar_system_target_id) | ||||
|     "[#{source_system_name}:#{target_system_name}]" | ||||
|   end | ||||
|  | ||||
|   defp _get_event_data(:map_connection_updated, %{ | ||||
|   defp get_event_data(:map_connection_updated, %{ | ||||
|          "key" => key, | ||||
|          "solar_system_source_id" => solar_system_source_id, | ||||
|          "solar_system_target_id" => solar_system_target_id, | ||||
|          "value" => value | ||||
|        }) do | ||||
|     source_system_name = _get_system_name(solar_system_source_id) | ||||
|     target_system_name = _get_system_name(solar_system_target_id) | ||||
|     source_system_name = get_system_name(solar_system_source_id) | ||||
|     target_system_name = get_system_name(solar_system_target_id) | ||||
|     "[#{source_system_name}:#{target_system_name}] #{key} - #{inspect(value)}" | ||||
|   end | ||||
|  | ||||
|   defp _get_event_data(_name, data), do: Jason.encode!(data) | ||||
|   defp get_event_data(_name, data), do: Jason.encode!(data) | ||||
|  | ||||
|   defp _get_system_name(solar_system_id) do | ||||
|   defp get_system_name(solar_system_id) do | ||||
|     case WandererApp.CachedInfo.get_system_static_info(solar_system_id) do | ||||
|       {:ok, nil} -> | ||||
|         solar_system_id | ||||
|   | ||||
| @@ -156,7 +156,7 @@ defmodule WandererAppWeb.MapCharactersEventHandler do | ||||
|  | ||||
|     %{result: characters} = socket.assigns.characters | ||||
|  | ||||
|     {:ok, map_characters} = get_tracked_map_characters(map_id, current_user) | ||||
|     {:ok, map_characters} = WandererApp.Maps.get_tracked_map_characters(map_id, current_user) | ||||
|  | ||||
|     user_character_eve_ids = map_characters |> Enum.map(& &1.eve_id) | ||||
|  | ||||
| @@ -204,7 +204,7 @@ defmodule WandererAppWeb.MapCharactersEventHandler do | ||||
|     {:ok, all_settings} = WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id) | ||||
|  | ||||
|     # Find and filter user's characters | ||||
|     {:ok, user_characters} = get_tracked_map_characters(map_id, current_user) | ||||
|     {:ok, user_characters} = WandererApp.Maps.get_tracked_map_characters(map_id, current_user) | ||||
|     user_char_ids = Enum.map(user_characters, & &1.id) | ||||
|  | ||||
|     my_settings = | ||||
| @@ -213,7 +213,7 @@ defmodule WandererAppWeb.MapCharactersEventHandler do | ||||
|         s.character_id in user_char_ids | ||||
|       end) | ||||
|  | ||||
|     existing = Enum.find(my_settings, &(&1.character_id == clicked_char_id)) | ||||
|     existing = Enum.find(all_settings, &(&1.character_id == clicked_char_id)) | ||||
|  | ||||
|     {:ok, target_setting} = | ||||
|       if not is_nil(existing) do | ||||
| @@ -251,7 +251,7 @@ defmodule WandererAppWeb.MapCharactersEventHandler do | ||||
|     # re-fetch or re-map to confirm final results in UI | ||||
|     %{result: characters} = socket.assigns.characters | ||||
|  | ||||
|     {:ok, tracked_characters} = get_tracked_map_characters(map_id, current_user) | ||||
|     {:ok, tracked_characters} = WandererApp.Maps.get_tracked_map_characters(map_id, current_user) | ||||
|     user_eve_ids = Enum.map(tracked_characters, & &1.eve_id) | ||||
|  | ||||
|     {:ok, final_settings} = WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id) | ||||
| @@ -316,21 +316,6 @@ defmodule WandererAppWeb.MapCharactersEventHandler do | ||||
|   def has_tracked_characters?([]), do: false | ||||
|   def has_tracked_characters?(_user_characters), do: true | ||||
|  | ||||
|   def get_tracked_map_characters(map_id, current_user) do | ||||
|     case WandererApp.MapCharacterSettingsRepo.get_tracked_by_map_filtered( | ||||
|            map_id, | ||||
|            current_user.characters |> Enum.map(& &1.id) | ||||
|          ) do | ||||
|       {:ok, settings} -> | ||||
|         {:ok, | ||||
|          settings | ||||
|          |> Enum.map(fn s -> s |> Ash.load!(:character) |> Map.get(:character) end)} | ||||
|  | ||||
|       _ -> | ||||
|         {:ok, []} | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def map_ui_character(character), | ||||
|     do: | ||||
|       character | ||||
|   | ||||
| @@ -119,10 +119,9 @@ defmodule WandererAppWeb.MapCoreEventHandler do | ||||
|       ), | ||||
|       do: socket | ||||
|  | ||||
|     def handle_server_event(%{event: :structures_updated, payload: _solar_system_id}, socket) do | ||||
|       socket | ||||
|     end | ||||
|  | ||||
|   def handle_server_event(%{event: :structures_updated, payload: _solar_system_id}, socket) do | ||||
|     socket | ||||
|   end | ||||
|  | ||||
|   def handle_server_event(event, socket) do | ||||
|     Logger.warning(fn -> "unhandled map core event: #{inspect(event)} #{inspect(socket)} " end) | ||||
| @@ -271,6 +270,8 @@ defmodule WandererAppWeb.MapCoreEventHandler do | ||||
|         current_user.characters |> Enum.map(& &1.id) | ||||
|       ) | ||||
|  | ||||
|     {:ok, map_user_settings} = WandererApp.MapUserSettingsRepo.get(map_id, current_user.id) | ||||
|  | ||||
|     {:ok, character_settings} = | ||||
|       case WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id) do | ||||
|         {:ok, settings} -> {:ok, settings} | ||||
| @@ -303,6 +304,7 @@ defmodule WandererAppWeb.MapCoreEventHandler do | ||||
|         socket | ||||
|         |> assign( | ||||
|           map_id: map_id, | ||||
|           map_user_settings: map_user_settings, | ||||
|           page_title: map_name, | ||||
|           user_permissions: user_permissions, | ||||
|           tracked_character_ids: tracked_character_ids, | ||||
| @@ -335,9 +337,8 @@ defmodule WandererAppWeb.MapCoreEventHandler do | ||||
|          } = socket | ||||
|        ) do | ||||
|     with {:ok, _} <- current_user |> WandererApp.Api.User.update_last_map(%{last_map_id: map_id}), | ||||
|          {:ok, map_user_settings} <- WandererApp.MapUserSettingsRepo.get(map_id, current_user.id), | ||||
|          {:ok, tracked_map_characters} <- | ||||
|            MapCharactersEventHandler.get_tracked_map_characters(map_id, current_user), | ||||
|            WandererApp.Maps.get_tracked_map_characters(map_id, current_user), | ||||
|          {:ok, characters_limit} <- map_id |> WandererApp.Map.get_characters_limit(), | ||||
|          {:ok, present_character_ids} <- | ||||
|            WandererApp.Cache.lookup("map_#{map_id}:presence_character_ids", []), | ||||
| @@ -415,7 +416,6 @@ defmodule WandererAppWeb.MapCoreEventHandler do | ||||
|       socket | ||||
|       |> map_start(%{ | ||||
|         map_id: map_id, | ||||
|         map_user_settings: map_user_settings, | ||||
|         user_characters: user_character_eve_ids, | ||||
|         initial_data: initial_data, | ||||
|         events: events | ||||
| @@ -438,7 +438,6 @@ defmodule WandererAppWeb.MapCoreEventHandler do | ||||
|          socket, | ||||
|          %{ | ||||
|            map_id: map_id, | ||||
|            map_user_settings: map_user_settings, | ||||
|            user_characters: user_character_eve_ids, | ||||
|            initial_data: initial_data, | ||||
|            events: events | ||||
| @@ -469,7 +468,6 @@ defmodule WandererAppWeb.MapCoreEventHandler do | ||||
|       socket | ||||
|       |> assign( | ||||
|         map_loaded?: true, | ||||
|         map_user_settings: map_user_settings, | ||||
|         user_characters: user_character_eve_ids, | ||||
|         has_tracked_characters?: has_tracked_characters? | ||||
|       ) | ||||
| @@ -543,21 +541,6 @@ defmodule WandererAppWeb.MapCoreEventHandler do | ||||
|     } | ||||
|   end | ||||
|  | ||||
|   defp get_tracked_map_characters(map_id, current_user) do | ||||
|     case WandererApp.MapCharacterSettingsRepo.get_tracked_by_map_filtered( | ||||
|            map_id, | ||||
|            current_user.characters |> Enum.map(& &1.id) | ||||
|          ) do | ||||
|       {:ok, settings} -> | ||||
|         {:ok, | ||||
|          settings | ||||
|          |> Enum.map(fn s -> s |> Ash.load!(:character) |> Map.get(:character) end)} | ||||
|  | ||||
|       _ -> | ||||
|         {:ok, []} | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   defp filter_map_characters( | ||||
|          characters, | ||||
|          user_character_eve_ids, | ||||
|   | ||||
| @@ -81,6 +81,7 @@ defmodule WandererAppWeb.MapSignaturesEventHandler do | ||||
|         }, | ||||
|         %{ | ||||
|           assigns: %{ | ||||
|             current_user: current_user, | ||||
|             map_id: map_id, | ||||
|             map_user_settings: map_user_settings, | ||||
|             user_characters: user_characters, | ||||
| @@ -129,7 +130,7 @@ defmodule WandererAppWeb.MapSignaturesEventHandler do | ||||
|               if delete_connection_with_sigs && not is_nil(s.linked_system_id) do | ||||
|                 map_id | ||||
|                 |> WandererApp.Map.Server.delete_connection(%{ | ||||
|                   solar_system_source_id: solar_system_id |> String.to_integer(), | ||||
|                   solar_system_source_id: system.solar_system_id, | ||||
|                   solar_system_target_id: s.linked_system_id | ||||
|                 }) | ||||
|               end | ||||
| @@ -161,10 +162,40 @@ defmodule WandererAppWeb.MapSignaturesEventHandler do | ||||
|             end) | ||||
|  | ||||
|             added_signatures | ||||
|             |> Enum.map(fn s -> | ||||
|             |> Enum.each(fn s -> | ||||
|               s |> WandererApp.Api.MapSystemSignature.create!() | ||||
|             end) | ||||
|  | ||||
|             added_signatures_eve_ids = | ||||
|               added_signatures | ||||
|               |> Enum.map(fn s -> s.eve_id end) | ||||
|  | ||||
|             first_tracked_character = | ||||
|               current_user.characters | ||||
|               |> Enum.find(fn c -> c.eve_id === first_character_eve_id end) | ||||
|  | ||||
|             if not is_nil(first_tracked_character) && | ||||
|                  not (added_signatures_eve_ids |> Enum.empty?()) do | ||||
|               WandererApp.User.ActivityTracker.track_map_event(:signatures_added, %{ | ||||
|                 character_id: first_tracked_character.id, | ||||
|                 user_id: current_user.id, | ||||
|                 map_id: map_id, | ||||
|                 solar_system_id: system.solar_system_id, | ||||
|                 signatures: added_signatures_eve_ids | ||||
|               }) | ||||
|             end | ||||
|  | ||||
|             if not is_nil(first_tracked_character) && | ||||
|                  not (removed_signatures_eve_ids |> Enum.empty?()) do | ||||
|               WandererApp.User.ActivityTracker.track_map_event(:signatures_removed, %{ | ||||
|                 character_id: first_tracked_character.id, | ||||
|                 user_id: current_user.id, | ||||
|                 map_id: map_id, | ||||
|                 solar_system_id: system.solar_system_id, | ||||
|                 signatures: removed_signatures_eve_ids | ||||
|               }) | ||||
|             end | ||||
|  | ||||
|             Phoenix.PubSub.broadcast!(WandererApp.PubSub, map_id, %{ | ||||
|               event: :signatures_updated, | ||||
|               payload: system.solar_system_id | ||||
|   | ||||
| @@ -21,57 +21,63 @@ defmodule WandererAppWeb.MapSystemsEventHandler do | ||||
|       |> MapEventHandler.push_map_event("remove_systems", solar_system_ids) | ||||
|  | ||||
|   def handle_server_event( | ||||
|       %{ | ||||
|         event: :maybe_select_system, | ||||
|         payload: %{ | ||||
|           character_id: character_id, | ||||
|           solar_system_id: solar_system_id | ||||
|         } | ||||
|       }, | ||||
|       %{assigns: %{current_user: current_user, map_id: map_id, map_user_settings: map_user_settings}} = socket | ||||
|     ) do | ||||
|         %{ | ||||
|           event: :maybe_select_system, | ||||
|           payload: %{ | ||||
|             character_id: character_id, | ||||
|             solar_system_id: solar_system_id | ||||
|           } | ||||
|         }, | ||||
|         %{ | ||||
|           assigns: %{ | ||||
|             current_user: current_user, | ||||
|             map_id: map_id, | ||||
|             map_user_settings: map_user_settings | ||||
|           } | ||||
|         } = socket | ||||
|       ) do | ||||
|     is_user_character = | ||||
|       current_user.characters | ||||
|       |> Enum.map(& &1.id) | ||||
|       |> Enum.member?(character_id) | ||||
|  | ||||
|       is_user_character = | ||||
|         current_user.characters | ||||
|         |> Enum.map(& &1.id) | ||||
|         |> Enum.member?(character_id) | ||||
|     is_select_on_spash = | ||||
|       map_user_settings | ||||
|       |> WandererApp.MapUserSettingsRepo.to_form_data!() | ||||
|       |> WandererApp.MapUserSettingsRepo.get_boolean_setting("select_on_spash") | ||||
|  | ||||
|       is_select_on_spash = | ||||
|         map_user_settings | ||||
|         |> WandererApp.MapUserSettingsRepo.to_form_data!() | ||||
|         |> WandererApp.MapUserSettingsRepo.get_boolean_setting("select_on_spash") | ||||
|     is_followed = | ||||
|       case WandererApp.MapCharacterSettingsRepo.get_by_map(map_id, character_id) do | ||||
|         {:ok, setting} -> setting.followed == true | ||||
|         _ -> false | ||||
|       end | ||||
|  | ||||
|       is_followed = | ||||
|         case WandererApp.MapCharacterSettingsRepo.get_by_map(map_id, character_id) do | ||||
|           {:ok, setting} -> setting.followed == true | ||||
|           _ -> false | ||||
|         end | ||||
|     must_select? = is_user_character && (is_select_on_spash || is_followed) | ||||
|  | ||||
|       must_select? = is_user_character && (is_select_on_spash || is_followed) | ||||
|       if not must_select? do | ||||
|     if not must_select? do | ||||
|       socket | ||||
|     else | ||||
|       # Check if we already selected this exact system for this char: | ||||
|       last_selected = | ||||
|         WandererApp.Cache.lookup!( | ||||
|           "char:#{character_id}:map:#{map_id}:last_selected_system_id", | ||||
|           nil | ||||
|         ) | ||||
|  | ||||
|       if last_selected == solar_system_id do | ||||
|         # same system => skip | ||||
|         socket | ||||
|       else | ||||
|         # Check if we already selected this exact system for this char: | ||||
|         last_selected = | ||||
|           WandererApp.Cache.lookup!( | ||||
|             "char:#{character_id}:map:#{map_id}:last_selected_system_id", | ||||
|             nil | ||||
|           ) | ||||
|         # new system => update cache + push event | ||||
|         WandererApp.Cache.put( | ||||
|           "char:#{character_id}:map:#{map_id}:last_selected_system_id", | ||||
|           solar_system_id | ||||
|         ) | ||||
|  | ||||
|         if last_selected == solar_system_id do | ||||
|           # same system => skip | ||||
|           socket | ||||
|         else | ||||
|           # new system => update cache + push event | ||||
|           WandererApp.Cache.put( | ||||
|             "char:#{character_id}:map:#{map_id}:last_selected_system_id", | ||||
|             solar_system_id | ||||
|           ) | ||||
|  | ||||
|           socket | ||||
|           |> MapEventHandler.push_map_event("select_system", solar_system_id) | ||||
|         end | ||||
|         socket | ||||
|         |> MapEventHandler.push_map_event("select_system", solar_system_id) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def handle_server_event(%{event: :kills_updated, payload: kills}, socket) do | ||||
|   | ||||
| @@ -171,7 +171,9 @@ defmodule WandererAppWeb.MapAuditLive do | ||||
|       {"ACL Removed", :map_acl_removed}, | ||||
|       {"Connection Added", :map_connection_added}, | ||||
|       {"Connection Updated", :map_connection_updated}, | ||||
|       {"Connection Removed", :map_connection_removed} | ||||
|       {"Connection Removed", :map_connection_removed}, | ||||
|       {"Signatures Added", :signatures_added}, | ||||
|       {"Signatures Removed", :signatures_removed} | ||||
|     ]) | ||||
|     |> load_activity(1) | ||||
|   end | ||||
|   | ||||
| @@ -1,20 +1,3 @@ | ||||
| <main | ||||
|   id="map-events-list" | ||||
|   class="pt-20 w-full h-full col-span-2 lg:col-span-1 p-4 pl-20 pb-20 overflow-auto" | ||||
| > | ||||
|   <div class="flex flex-col gap-4 w-full"> | ||||
|     <.live_component | ||||
|       module={UserActivity} | ||||
|       id="user-activity" | ||||
|       notify_to={self()} | ||||
|       can_undo_types={@can_undo_types} | ||||
|       stream={@streams.activity} | ||||
|       page={@page} | ||||
|       end_of_stream?={@end_of_stream?} | ||||
|       event_name="activity_event" | ||||
|     /> | ||||
|   </div> | ||||
| </main> | ||||
| <nav class="fixed top-0 z-100 px-6 pl-20 flex items-center justify-between w-full h-12 pointer-events-auto border-b border-stone-800  bg-opacity-70 bg-neutral-900"> | ||||
|   <span className="w-full font-medium text-sm"> | ||||
|     <.link navigate={~p"/#{@map_slug}"} class="text-neutral-100"> | ||||
| @@ -113,3 +96,20 @@ | ||||
|     </div> | ||||
|   </div> | ||||
| </nav> | ||||
| <main | ||||
|   id="map-events-list" | ||||
|   class="pt-20 w-full h-full col-span-2 lg:col-span-1 p-4 pl-20 pb-20 overflow-auto" | ||||
| > | ||||
|   <div class="flex flex-col gap-4 w-full"> | ||||
|     <.live_component | ||||
|       module={UserActivity} | ||||
|       id="user-activity" | ||||
|       notify_to={self()} | ||||
|       can_undo_types={@can_undo_types} | ||||
|       stream={@streams.activity} | ||||
|       page={@page} | ||||
|       end_of_stream?={@end_of_stream?} | ||||
|       event_name="activity_event" | ||||
|     /> | ||||
|   </div> | ||||
| </main> | ||||
|   | ||||
| @@ -112,7 +112,7 @@ defmodule WandererAppWeb.MapsLive do | ||||
|     subscription_form = %{ | ||||
|       "plan" => "omega", | ||||
|       "period" => "1", | ||||
|       "characters_limit" => "300", | ||||
|       "characters_limit" => "100", | ||||
|       "hubs_limit" => "10", | ||||
|       "auto_renew?" => true | ||||
|     } | ||||
| @@ -636,6 +636,33 @@ defmodule WandererAppWeb.MapsLive do | ||||
|           {:map_acl_updated, added_acls, removed_acls} | ||||
|         ) | ||||
|  | ||||
|         {:ok, tracked_characters} = | ||||
|           WandererApp.Maps.get_tracked_map_characters(map.id, current_user) | ||||
|  | ||||
|         first_tracked_character_id = Enum.map(tracked_characters, & &1.id) |> List.first() | ||||
|  | ||||
|         added_acls | ||||
|         |> Enum.each(fn acl_id -> | ||||
|           {:ok, _} = | ||||
|             WandererApp.User.ActivityTracker.track_map_event(:map_acl_added, %{ | ||||
|               character_id: first_tracked_character_id, | ||||
|               user_id: current_user.id, | ||||
|               map_id: map.id, | ||||
|               acl_id: acl_id | ||||
|             }) | ||||
|         end) | ||||
|  | ||||
|         removed_acls | ||||
|         |> Enum.each(fn acl_id -> | ||||
|           {:ok, _} = | ||||
|             WandererApp.User.ActivityTracker.track_map_event(:map_acl_removed, %{ | ||||
|               character_id: first_tracked_character_id, | ||||
|               user_id: current_user.id, | ||||
|               map_id: map.id, | ||||
|               acl_id: acl_id | ||||
|             }) | ||||
|         end) | ||||
|  | ||||
|         {:noreply, | ||||
|          socket | ||||
|          |> assign_async(:maps, fn -> | ||||
|   | ||||
| @@ -580,11 +580,9 @@ | ||||
|               > | ||||
|                 <div :if={is_nil(@selected_subscription)}> | ||||
|                   Add subscription | ||||
|                   <div class="badge badge-secondary">Limited time offer: 50%</div> | ||||
|                 </div> | ||||
|                 <div :if={not is_nil(@selected_subscription)}> | ||||
|                   Edit subscription | ||||
|                   <div class="badge badge-secondary">Limited time offer: 50%</div> | ||||
|                 </div> | ||||
|                 <.form | ||||
|                   :let={f} | ||||
| @@ -609,7 +607,7 @@ | ||||
|                     label="Characters limit" | ||||
|                     show_value={true} | ||||
|                     type="range" | ||||
|                     min="300" | ||||
|                     min="100" | ||||
|                     max="5000" | ||||
|                     step="100" | ||||
|                     class="range range-xs" | ||||
|   | ||||
							
								
								
									
										2
									
								
								mix.exs
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								mix.exs
									
									
									
									
									
								
							| @@ -3,7 +3,7 @@ defmodule WandererApp.MixProject do | ||||
|  | ||||
|   @source_url "https://github.com/wanderer-industries/wanderer" | ||||
|    | ||||
|   @version "1.40.5" | ||||
|   @version "1.43.8" | ||||
|  | ||||
|   def project do | ||||
|     [ | ||||
|   | ||||
							
								
								
									
										151
									
								
								priv/posts/2025/01-20-structure-widget.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								priv/posts/2025/01-20-structure-widget.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,151 @@ | ||||
| %{ | ||||
| title: "Managing Upwell Structures & Timers with the Structures Widget", | ||||
| author: "Wanderer Team", | ||||
| cover_image_uri: "/images/news/01-20-structure-widget/cover.png", | ||||
| tags: ~w(interface guide map structures), | ||||
| description: "Learn how to track structure information using the Structures Widget." | ||||
| } | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### Introduction | ||||
|  | ||||
| Upwell structures like **Astrahus**, **Athanor**, and more are key strategic points in EVE Online. Staying informed about their statuses—whether they’re anchoring, powered, or reinforced—helps you plan defenses, coordinate attacks, and align with allies. Our **Structures Widget** simplifies the process by allowing you to: | ||||
|  | ||||
| - Copy structure information directly from the in-game Directional Scanner (`D-Scan`) and paste it into the widget. | ||||
| - Keep track of **anchoring** or **reinforced** timers, including exact vulnerability windows. | ||||
| - Share real-time data across the map with your corporation or alliance, ensuring everyone is on the same page. | ||||
|  | ||||
| In this guide, we’ll explore how to enable the Structures Widget, manage structure data, and make use of the built-in API for remote structure updates. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### 1. Enabling the Structure Widget | ||||
|  | ||||
|  | ||||
|  | ||||
| 1. **Open the Map:** | ||||
| 2. **Locate the Widget Settings:** By default, the structure widget panel is not visible.  Enable it by going to menu -> map settings -> widgets. | ||||
| 3. **Add the Structures Widget:** Click the checkbox for **Structures** from the list of available widgets. | ||||
|  | ||||
| > **Tip:** Rearrange your widgets by dragging them around the panel to suit your workflow. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### 2. Overview of the Structures Widget | ||||
|  | ||||
|  | ||||
|  | ||||
| Once enabled, the **Structures Widget** appears in the map. It shows: | ||||
|  | ||||
| - **Structure Type** (Astrahus, Fortizar, etc.) | ||||
| - **Structure Name** (auto-detected if you paste from D-Scan) | ||||
| - **Owner** (Corporation ticker) | ||||
| - **Status** (Powered, Anchoring, Low Power, Reinforced, etc.) | ||||
| - **Timer** (Reinforced or anchoring end time) | ||||
|  | ||||
| You can **click** or **double-click** on an entry to edit details like the structure’s owner or add notes about the structure’s purpose or location. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### 3. Adding Structures via Copy & Paste | ||||
|  | ||||
| A fast way to add structure data is by copying from in-game D-Scan or show-info panels: | ||||
|  | ||||
| 1. **In EVE Online:** Open the D-Scan window or structure context menu, select the relevant lines of text, and press **Ctrl + C**. | ||||
| 2. **In the Widget:** Focus on the Structures Widget, click in the widget area, and press **Ctrl + V** to paste or use the **blue** add structure info button. | ||||
| 3. The widget automatically parses the structure names and types. You can also add owners and notes manually. | ||||
|  | ||||
| This eliminates manual typing and reduces the chance of errors, especially useful when scanning multiple systems. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### 4. Tracking Reinforced Timers | ||||
|  | ||||
| When a structure is in a **Reinforced** or **Anchoring** state, we have a timer to note when it becomes vulnerable or completes anchoring: | ||||
|  | ||||
| - **Timer Field:** If the structure’s status is set to “Reinforced” or “Anchoring,” the widget enables a **Calendar** pop-up where you can set the _end time_. | ||||
|  | ||||
| Keep your fleet prepared by referencing this schedule. When the timer hits zero, the structure becomes vulnerable (or finishes anchoring). | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### 5. Editing and Deleting Structures | ||||
|  | ||||
| 1. **Single-click** a structure entry to select it. | ||||
| 2. Press **Delete** (or **Backspace**) to remove it entirely—useful when clearing out old data or removing outdated structures. | ||||
| 3. **Double-click** to open the **Edit Dialog**: | ||||
|    - Change **Name**, **Owner**, or **Status**. | ||||
|    - Update or remove **Reinforced** timers. | ||||
|    - Add or edit **Notes**. | ||||
|  | ||||
| Any changes made here are immediately visible to other map users. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### 6. API Integration for Automated Timers | ||||
|  | ||||
| Beyond the in-app widget, there is a dedicated API endpoint to fetch or update structure timers programmatically. This allows advanced users and third-party applications to seamlessly incorporate structure data. | ||||
|  | ||||
| **Example API Request/Response**: | ||||
|  | ||||
| ```bash | ||||
| curl -H "Authorization: Bearer YOUR_API_TOKEN" \ | ||||
| "https://wanderer.yourdomain.space/api/map/structure-timers?slug=yourmap" | ||||
|  | ||||
|   "data": [ | ||||
|     { | ||||
|       "name": "Overlook Hotel", | ||||
|       "status": "Reinforced", | ||||
|       "notes": null, | ||||
|       "owner_id": null, | ||||
|       "solar_system_id": 31000515, | ||||
|       "solar_system_name": "J114942", | ||||
|       "character_eve_id": "2122839817", | ||||
|       "system_id": "4865aec4-b69d-4524-91d3-250b0556322b", | ||||
|       "end_time": "2025-01-22T23:42:03.000000Z", | ||||
|       "owner_name": null, | ||||
|       "owner_ticker": null, | ||||
|       "structure_type": "Astrahus", | ||||
|       "structure_type_id": "35832" | ||||
|     }, | ||||
|     { | ||||
|       "name": "Some Structure", | ||||
|       "status": "Reinforced", | ||||
|       "notes": null, | ||||
|       "owner_id": null, | ||||
|       "solar_system_id": 3100229, | ||||
|       "solar_system_name": "somecustomname", | ||||
|       "character_eve_id": "some name", | ||||
|       "system_id": "ae779ed6-92b3-4349-899d-f1bdf299082f", | ||||
|       "end_time": "2025-01-16T03:04:00.000000Z", | ||||
|       "owner_name": null, | ||||
|       "owner_ticker": null, | ||||
|       "structure_type": "Athanor", | ||||
|       "structure_type_id": "35835" | ||||
|     } | ||||
|   ] | ||||
| ``` | ||||
|  | ||||
|  | ||||
| With this API, you could, for example, build automated pings on Slack/Discord when timers are about to expire or display status updates on a custom web dashboard. | ||||
|  | ||||
| > **Note:** Ensure your API token (`Bearer YOUR_API_TOKEN`) matches the api key generated for you map. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### 7. Best Practices & Tips | ||||
|  | ||||
| - **Keep Data Fresh:** Update timers as soon as possible after a structure enters reinforcement. This keeps your corporation or alliance fully informed. | ||||
| - **Use Notes Effectively:** Add details such as final reinforcement phases or relevant system intel (e.g., known hostiles, safe spots) to help allies plan more effectively. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Conclusion | ||||
|  | ||||
| The **Structures Widget** is your central hub for monitoring, updating, and sharing information about Upwell structures across New Eden. From real-time timer tracking to simple copy-and-paste integration with D-Scan, this widget streamlines group operations and cuts down on manual data entry. | ||||
|  | ||||
| Whether you’re a solo explorer managing a personal citadel network or a fleet commander overseeing multiple staging systems, the Structures Widget and its accompanying API ensure you’ll always have up-to-date intel on the structures that matter most. | ||||
|  | ||||
| Fly safe, | ||||
| **The Wanderer Team** | ||||
| @@ -1,8 +1,9 @@ | ||||
| { | ||||
|     "21000325": true, | ||||
|     "21000326": true, | ||||
|     "21000327": true, | ||||
|     "21000328": true, | ||||
|     "21000329": true, | ||||
|     "21000330": true | ||||
| } | ||||
|   "21000325": true, | ||||
|   "21000326": true, | ||||
|   "21000327": true, | ||||
|   "21000328": true, | ||||
|   "21000329": true, | ||||
|   "21000330": true, | ||||
|   "21000333": true | ||||
| } | ||||
|   | ||||
| @@ -222,7 +222,7 @@ | ||||
|   { | ||||
|     "mass_regen": 500000000, | ||||
|     "dest": "hs", | ||||
|     "src": ["c3"], | ||||
|     "src": ["c3", "c4-shattered"], | ||||
|     "static": true, | ||||
|     "max_mass_per_jump": 300000000, | ||||
|     "lifetime": "24", | ||||
|   | ||||
| @@ -0,0 +1,198 @@ | ||||
| { | ||||
|   "attributes": [ | ||||
|     { | ||||
|       "allow_nil?": false, | ||||
|       "default": "fragment(\"gen_random_uuid()\")", | ||||
|       "generated?": false, | ||||
|       "primary_key?": true, | ||||
|       "references": null, | ||||
|       "size": null, | ||||
|       "source": "id", | ||||
|       "type": "uuid" | ||||
|     }, | ||||
|     { | ||||
|       "allow_nil?": false, | ||||
|       "default": "nil", | ||||
|       "generated?": false, | ||||
|       "primary_key?": false, | ||||
|       "references": null, | ||||
|       "size": null, | ||||
|       "source": "structure_type_id", | ||||
|       "type": "text" | ||||
|     }, | ||||
|     { | ||||
|       "allow_nil?": false, | ||||
|       "default": "nil", | ||||
|       "generated?": false, | ||||
|       "primary_key?": false, | ||||
|       "references": null, | ||||
|       "size": null, | ||||
|       "source": "structure_type", | ||||
|       "type": "text" | ||||
|     }, | ||||
|     { | ||||
|       "allow_nil?": false, | ||||
|       "default": "nil", | ||||
|       "generated?": false, | ||||
|       "primary_key?": false, | ||||
|       "references": null, | ||||
|       "size": null, | ||||
|       "source": "character_eve_id", | ||||
|       "type": "text" | ||||
|     }, | ||||
|     { | ||||
|       "allow_nil?": false, | ||||
|       "default": "nil", | ||||
|       "generated?": false, | ||||
|       "primary_key?": false, | ||||
|       "references": null, | ||||
|       "size": null, | ||||
|       "source": "solar_system_name", | ||||
|       "type": "text" | ||||
|     }, | ||||
|     { | ||||
|       "allow_nil?": false, | ||||
|       "default": "nil", | ||||
|       "generated?": false, | ||||
|       "primary_key?": false, | ||||
|       "references": null, | ||||
|       "size": null, | ||||
|       "source": "solar_system_id", | ||||
|       "type": "bigint" | ||||
|     }, | ||||
|     { | ||||
|       "allow_nil?": false, | ||||
|       "default": "nil", | ||||
|       "generated?": false, | ||||
|       "primary_key?": false, | ||||
|       "references": null, | ||||
|       "size": null, | ||||
|       "source": "name", | ||||
|       "type": "text" | ||||
|     }, | ||||
|     { | ||||
|       "allow_nil?": true, | ||||
|       "default": "nil", | ||||
|       "generated?": false, | ||||
|       "primary_key?": false, | ||||
|       "references": null, | ||||
|       "size": null, | ||||
|       "source": "notes", | ||||
|       "type": "text" | ||||
|     }, | ||||
|     { | ||||
|       "allow_nil?": true, | ||||
|       "default": "nil", | ||||
|       "generated?": false, | ||||
|       "primary_key?": false, | ||||
|       "references": null, | ||||
|       "size": null, | ||||
|       "source": "owner_name", | ||||
|       "type": "text" | ||||
|     }, | ||||
|     { | ||||
|       "allow_nil?": true, | ||||
|       "default": "nil", | ||||
|       "generated?": false, | ||||
|       "primary_key?": false, | ||||
|       "references": null, | ||||
|       "size": null, | ||||
|       "source": "owner_ticker", | ||||
|       "type": "text" | ||||
|     }, | ||||
|     { | ||||
|       "allow_nil?": true, | ||||
|       "default": "nil", | ||||
|       "generated?": false, | ||||
|       "primary_key?": false, | ||||
|       "references": null, | ||||
|       "size": null, | ||||
|       "source": "owner_id", | ||||
|       "type": "text" | ||||
|     }, | ||||
|     { | ||||
|       "allow_nil?": true, | ||||
|       "default": "nil", | ||||
|       "generated?": false, | ||||
|       "primary_key?": false, | ||||
|       "references": null, | ||||
|       "size": null, | ||||
|       "source": "status", | ||||
|       "type": "text" | ||||
|     }, | ||||
|     { | ||||
|       "allow_nil?": true, | ||||
|       "default": "nil", | ||||
|       "generated?": false, | ||||
|       "primary_key?": false, | ||||
|       "references": null, | ||||
|       "size": null, | ||||
|       "source": "end_time", | ||||
|       "type": "utc_datetime_usec" | ||||
|     }, | ||||
|     { | ||||
|       "allow_nil?": false, | ||||
|       "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", | ||||
|       "generated?": false, | ||||
|       "primary_key?": false, | ||||
|       "references": null, | ||||
|       "size": null, | ||||
|       "source": "inserted_at", | ||||
|       "type": "utc_datetime_usec" | ||||
|     }, | ||||
|     { | ||||
|       "allow_nil?": false, | ||||
|       "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", | ||||
|       "generated?": false, | ||||
|       "primary_key?": false, | ||||
|       "references": null, | ||||
|       "size": null, | ||||
|       "source": "updated_at", | ||||
|       "type": "utc_datetime_usec" | ||||
|     }, | ||||
|     { | ||||
|       "allow_nil?": true, | ||||
|       "default": "nil", | ||||
|       "generated?": false, | ||||
|       "primary_key?": false, | ||||
|       "references": { | ||||
|         "deferrable": false, | ||||
|         "destination_attribute": "id", | ||||
|         "destination_attribute_default": null, | ||||
|         "destination_attribute_generated": null, | ||||
|         "index?": false, | ||||
|         "match_type": null, | ||||
|         "match_with": null, | ||||
|         "multitenancy": { | ||||
|           "attribute": null, | ||||
|           "global": null, | ||||
|           "strategy": null | ||||
|         }, | ||||
|         "name": "map_system_structures_v1_system_id_fkey", | ||||
|         "on_delete": null, | ||||
|         "on_update": null, | ||||
|         "primary_key?": true, | ||||
|         "schema": "public", | ||||
|         "table": "map_system_v1" | ||||
|       }, | ||||
|       "size": null, | ||||
|       "source": "system_id", | ||||
|       "type": "uuid" | ||||
|     } | ||||
|   ], | ||||
|   "base_filter": null, | ||||
|   "check_constraints": [], | ||||
|   "custom_indexes": [], | ||||
|   "custom_statements": [], | ||||
|   "has_create_action": true, | ||||
|   "hash": "B9DA704034C53F0EC20C28EED99D579A34034655225EDC3BC7E57719B276F83F", | ||||
|   "identities": [], | ||||
|   "multitenancy": { | ||||
|     "attribute": null, | ||||
|     "global": null, | ||||
|     "strategy": null | ||||
|   }, | ||||
|   "repo": "Elixir.WandererApp.Repo", | ||||
|   "schema": null, | ||||
|   "table": "map_system_structures_v1" | ||||
| } | ||||
		Reference in New Issue
	
	Block a user