feat(go, ts): add system-region mapping and highlight functionality
This commit is contained in:
22
app.go
22
app.go
@@ -138,3 +138,25 @@ func (a *App) GetCharacterLocations() ([]CharacterLocation, error) {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
return a.ssi.GetCharacterLocations(ctx)
|
return a.ssi.GetCharacterLocations(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SystemRegion holds system + region names from local DB
|
||||||
|
type SystemRegion struct {
|
||||||
|
System string `json:"system"`
|
||||||
|
Region string `json:"region"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListSystemsWithRegions returns all solar system names and their regions from the local SQLite DB
|
||||||
|
func (a *App) ListSystemsWithRegions() ([]SystemRegion, error) {
|
||||||
|
if a.ssi == nil || a.ssi.db == nil {
|
||||||
|
return nil, errors.New("db not initialised")
|
||||||
|
}
|
||||||
|
var rows []SystemRegion
|
||||||
|
// mapSolarSystems has regionID; mapRegions has regionName
|
||||||
|
q := `SELECT s.solarSystemName AS system, r.regionName AS region
|
||||||
|
FROM mapSolarSystems s
|
||||||
|
JOIN mapRegions r ON r.regionID = s.regionID`
|
||||||
|
if err := a.ssi.db.Raw(q).Scan(&rows).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return rows, nil
|
||||||
|
}
|
||||||
|
@@ -98,6 +98,25 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
|
|||||||
const [offRegionIndicators, setOffRegionIndicators] = useState<OffIndicator[]>([]);
|
const [offRegionIndicators, setOffRegionIndicators] = useState<OffIndicator[]>([]);
|
||||||
const [meanNeighborAngle, setMeanNeighborAngle] = useState<Record<string, number>>({});
|
const [meanNeighborAngle, setMeanNeighborAngle] = useState<Record<string, number>>({});
|
||||||
const [charLocs, setCharLocs] = useState<Array<{ character_id: number; character_name: string; solar_system_name: string }>>([]);
|
const [charLocs, setCharLocs] = useState<Array<{ character_id: number; character_name: string; solar_system_name: string }>>([]);
|
||||||
|
const [focusUntil, setFocusUntil] = useState<number | null>(null);
|
||||||
|
|
||||||
|
// When focusSystem changes, set an expiry 20s in the future
|
||||||
|
useEffect(() => {
|
||||||
|
if (focusSystem) {
|
||||||
|
setFocusUntil(Date.now() + 20000);
|
||||||
|
}
|
||||||
|
}, [focusSystem]);
|
||||||
|
|
||||||
|
// Timer to clear focus after expiry
|
||||||
|
useEffect(() => {
|
||||||
|
if (!focusUntil) return;
|
||||||
|
const id = setInterval(() => {
|
||||||
|
if (Date.now() > focusUntil) {
|
||||||
|
setFocusUntil(null);
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
return () => clearInterval(id);
|
||||||
|
}, [focusUntil]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onKeyDown = async (e: KeyboardEvent) => {
|
const onKeyDown = async (e: KeyboardEvent) => {
|
||||||
@@ -700,27 +719,35 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
|
|||||||
})}
|
})}
|
||||||
|
|
||||||
{/* Highlight focused system */}
|
{/* Highlight focused system */}
|
||||||
{focusSystem && positions[focusSystem] && (
|
{focusSystem && focusUntil && Date.now() <= focusUntil && positions[focusSystem] && (
|
||||||
<circle
|
<g>
|
||||||
cx={positions[focusSystem].x}
|
<circle
|
||||||
cy={positions[focusSystem].y}
|
cx={positions[focusSystem].x}
|
||||||
r="15"
|
cy={positions[focusSystem].y}
|
||||||
fill="none"
|
r="20"
|
||||||
stroke="#a855f7"
|
fill="none"
|
||||||
strokeWidth="3"
|
stroke="#f472b6"
|
||||||
strokeDasharray="5,5"
|
strokeWidth="4"
|
||||||
opacity="0.8"
|
strokeDasharray="6,6"
|
||||||
>
|
opacity="0.95"
|
||||||
<animateTransform
|
filter="url(#glow)"
|
||||||
attributeName="transform"
|
>
|
||||||
attributeType="XML"
|
<animate
|
||||||
type="rotate"
|
attributeName="r"
|
||||||
from={`0 ${positions[focusSystem].x} ${positions[focusSystem].y}`}
|
values="18;24;18"
|
||||||
to={`360 ${positions[focusSystem].x} ${positions[focusSystem].y}`}
|
dur="1.8s"
|
||||||
dur="10s"
|
repeatCount="indefinite"
|
||||||
repeatCount="indefinite"
|
/>
|
||||||
|
</circle>
|
||||||
|
<circle
|
||||||
|
cx={positions[focusSystem].x}
|
||||||
|
cy={positions[focusSystem].y}
|
||||||
|
r="8"
|
||||||
|
fill="#f472b6"
|
||||||
|
opacity="0.8"
|
||||||
/>
|
/>
|
||||||
</circle>
|
<text x={positions[focusSystem].x + 14} y={positions[focusSystem].y - 14} fontSize="10" fill="#ffffff" stroke="#0f172a" strokeWidth="2" paintOrder="stroke">{focusSystem}</text>
|
||||||
|
</g>
|
||||||
)}
|
)}
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
|
@@ -3,7 +3,6 @@ import { useNavigate } from 'react-router-dom';
|
|||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { AhoCorasick } from '@/lib/aho';
|
import { AhoCorasick } from '@/lib/aho';
|
||||||
import pb from '@/lib/pocketbase';
|
|
||||||
|
|
||||||
interface SearchResult {
|
interface SearchResult {
|
||||||
system: string;
|
system: string;
|
||||||
@@ -11,26 +10,22 @@ interface SearchResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadAllSystems(): Promise<Array<SearchResult>> {
|
async function loadAllSystems(): Promise<Array<SearchResult>> {
|
||||||
// Fetch system names with regions from PocketBase, paginated and minimal fields
|
// Fetch from Go (Wails): local SQLite DB via App.ListSystemsWithRegions
|
||||||
const perPage = 1000;
|
try {
|
||||||
let page = 1;
|
const list = await (window as any)?.go?.main?.App?.ListSystemsWithRegions?.();
|
||||||
const out: Array<SearchResult> = [];
|
if (Array.isArray(list)) {
|
||||||
const seen = new Set<string>();
|
const seen = new Set<string>();
|
||||||
// loop pages until fewer than perPage items
|
const out: Array<SearchResult> = [];
|
||||||
while (true) {
|
for (const item of list) {
|
||||||
const res = await pb.collection('regionview').getList(page, perPage, { fields: 'sysname,sysregion' });
|
const system = String(item.system);
|
||||||
for (const item of res.items as any[]) {
|
if (seen.has(system)) continue;
|
||||||
const system: string = item.sysname;
|
|
||||||
const region: string = item.sysregion;
|
|
||||||
if (!seen.has(system)) {
|
|
||||||
seen.add(system);
|
seen.add(system);
|
||||||
out.push({ system, region });
|
out.push({ system, region: String(item.region) });
|
||||||
}
|
}
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
if (res.items.length < perPage) break;
|
} catch (_) { /* noop */ }
|
||||||
page += 1;
|
return [];
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SearchDialog: React.FC = () => {
|
export const SearchDialog: React.FC = () => {
|
||||||
|
1
frontend/wails.json
Normal file
1
frontend/wails.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
2
frontend/wailsjs/go/main/App.d.ts
vendored
2
frontend/wailsjs/go/main/App.d.ts
vendored
@@ -14,6 +14,8 @@ export function Greet(arg1:string):Promise<string>;
|
|||||||
|
|
||||||
export function ListCharacters():Promise<Array<main.CharacterInfo>>;
|
export function ListCharacters():Promise<Array<main.CharacterInfo>>;
|
||||||
|
|
||||||
|
export function ListSystemsWithRegions():Promise<Array<main.SystemRegion>>;
|
||||||
|
|
||||||
export function PostRouteForAllByNames(arg1:string,arg2:Array<string>):Promise<void>;
|
export function PostRouteForAllByNames(arg1:string,arg2:Array<string>):Promise<void>;
|
||||||
|
|
||||||
export function SetDestinationForAll(arg1:string,arg2:boolean,arg3:boolean):Promise<void>;
|
export function SetDestinationForAll(arg1:string,arg2:boolean,arg3:boolean):Promise<void>;
|
||||||
|
@@ -26,6 +26,10 @@ export function ListCharacters() {
|
|||||||
return window['go']['main']['App']['ListCharacters']();
|
return window['go']['main']['App']['ListCharacters']();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ListSystemsWithRegions() {
|
||||||
|
return window['go']['main']['App']['ListSystemsWithRegions']();
|
||||||
|
}
|
||||||
|
|
||||||
export function PostRouteForAllByNames(arg1, arg2) {
|
export function PostRouteForAllByNames(arg1, arg2) {
|
||||||
return window['go']['main']['App']['PostRouteForAllByNames'](arg1, arg2);
|
return window['go']['main']['App']['PostRouteForAllByNames'](arg1, arg2);
|
||||||
}
|
}
|
||||||
|
@@ -53,6 +53,20 @@ export namespace main {
|
|||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export class SystemRegion {
|
||||||
|
system: string;
|
||||||
|
region: string;
|
||||||
|
|
||||||
|
static createFrom(source: any = {}) {
|
||||||
|
return new SystemRegion(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(source: any = {}) {
|
||||||
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
|
this.system = source["system"];
|
||||||
|
this.region = source["region"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user