diff --git a/app.go b/app.go index 6209a38..7159af6 100644 --- a/app.go +++ b/app.go @@ -58,7 +58,6 @@ func (a *App) StartESILogin() (string, error) { return url, nil } -// ESILoginStatus returns a short status string of the active token/character func (a *App) ESILoginStatus() string { if a.ssi == nil { return "not initialised" @@ -70,7 +69,6 @@ func (a *App) ESILoginStatus() string { return "not logged in" } -// ESILoggedIn returns true if a valid access token is present func (a *App) ESILoggedIn() bool { if a.ssi == nil { return false @@ -78,27 +76,6 @@ func (a *App) ESILoggedIn() bool { return a.ssi.Status().LoggedIn } -// SetDestination posts a waypoint to ESI to set destination -func (a *App) SetDestination(destinationID int64, clearOthers bool, addToBeginning bool) error { - if a.ssi == nil { - return errors.New("ESI not initialised") - } - return a.ssi.PostWaypoint(destinationID, clearOthers, addToBeginning) -} - -// SetDestinationByName resolves a solar system name to ID and sets destination -func (a *App) SetDestinationByName(systemName string, clearOthers bool, addToBeginning bool) error { - if a.ssi == nil { - return errors.New("ESI not initialised") - } - id, err := a.ssi.ResolveSystemIDByName(a.ctx, systemName) - if err != nil { - return err - } - return a.ssi.PostWaypoint(id, clearOthers, addToBeginning) -} - -// SetDestinationForAll resolves system name to ID and applies waypoint to all logged-in characters func (a *App) SetDestinationForAll(systemName string, clearOthers bool, addToBeginning bool) error { if a.ssi == nil { return errors.New("ESI not initialised") @@ -110,7 +87,6 @@ func (a *App) SetDestinationForAll(systemName string, clearOthers bool, addToBeg return a.ssi.PostWaypointForAll(id, clearOthers, addToBeginning) } -// AddWaypointForAllByName resolves system name and appends as waypoint for all characters func (a *App) AddWaypointForAllByName(systemName string, addToBeginning bool) error { if a.ssi == nil { return errors.New("ESI not initialised") @@ -122,7 +98,20 @@ func (a *App) AddWaypointForAllByName(systemName string, addToBeginning bool) er return a.ssi.PostWaypointForAll(id, false, addToBeginning) } -// ListCharacters returns all characters stored in the token DB +// PostRouteForAllByNames posts a full route: via names (in order) then destination at the end (clearing first) +func (a *App) PostRouteForAllByNames(destination string, vias []string) error { + if a.ssi == nil { + return errors.New("ESI not initialised") + } + ids, err := a.ssi.ResolveSystemIDsByNames(a.ctx, append(vias, destination)) + if err != nil { + return err + } + viaIDs := ids[:len(vias)] + destID := ids[len(vias)] + return a.ssi.PostRouteForAll(destID, viaIDs) +} + func (a *App) ListCharacters() ([]CharacterInfo, error) { if a.ssi == nil || a.ssi.db == nil { return nil, errors.New("ESI not initialised") diff --git a/esi_sso.go b/esi_sso.go index cc27366..660df59 100644 --- a/esi_sso.go +++ b/esi_sso.go @@ -670,4 +670,81 @@ func (s *ESISSO) ResolveSystemIDByName(ctx context.Context, name string) (int64, } fmt.Printf("ESI: names resolved fallback: returning %d for %q\n", namesResp[0].ID, name) return namesResp[0].ID, nil +} + +// ResolveSystemIDsByNames returns IDs in the same order as names. Missing entries error. +func (s *ESISSO) ResolveSystemIDsByNames(ctx context.Context, names []string) ([]int64, error) { + ordered := make([]int64, len(names)) + if len(names) == 0 { + return ordered, nil + } + // DB first + nameLower := make([]string, len(names)) + for i, n := range names { nameLower[i] = strings.TrimSpace(n) } + found := map[string]int64{} + if s.db != nil { + var rows []SolarSystem + if err := s.db.Where("solarSystemName IN ?", nameLower).Find(&rows).Error; err == nil { + for _, r := range rows { + found[r.SolarSystemName] = r.SolarSystemID + } + } + } + // Collect missing for ESI batch + missing := []string{} + for _, n := range nameLower { + if _, ok := found[n]; !ok { + missing = append(missing, n) + } + } + if len(missing) > 0 { + idsURL := esiBase + "/v3/universe/ids/?datasource=tranquility" + body, _ := json.Marshal(struct{ Names []string `json:"names"` }{Names: missing}) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, idsURL, strings.NewReader(string(body))) + if err != nil { return nil, err } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Accept", "application/json") + resp, err := http.DefaultClient.Do(req) + if err != nil { return nil, err } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + b, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("universe/ids failed: %s: %s", resp.Status, string(b)) + } + var idsResp struct{ Systems []struct{ ID int64 `json:"id"`; Name string `json:"name"` } `json:"systems"` } + if err := json.NewDecoder(resp.Body).Decode(&idsResp); err != nil { return nil, err } + for _, it := range idsResp.Systems { found[it.Name] = it.ID } + } + // Build ordered list + for i, n := range nameLower { + id, ok := found[n] + if !ok { return nil, fmt.Errorf("system not found: %s", n) } + ordered[i] = id + } + return ordered, nil +} + +// PostRouteForAll clears route and posts vias then destination last +func (s *ESISSO) PostRouteForAll(destID int64, viaIDs []int64) error { + if s.db == nil { return errors.New("db not initialised") } + var tokens []ESIToken + if err := s.db.Find(&tokens).Error; err != nil { return err } + var firstErr error + for i := range tokens { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + tok, err := s.ensureAccessTokenFor(ctx, &tokens[i]) + cancel() + if err != nil { if firstErr == nil { firstErr = err }; continue } + // Determine sequence: clear with first element (via if any, else dest) + if len(viaIDs) > 0 { + if err := s.postWaypointWithToken(tok, viaIDs[0], true, false); err != nil && firstErr == nil { firstErr = err } + for _, id := range viaIDs[1:] { + if err := s.postWaypointWithToken(tok, id, false, false); err != nil && firstErr == nil { firstErr = err } + } + if err := s.postWaypointWithToken(tok, destID, false, false); err != nil && firstErr == nil { firstErr = err } + } else { + if err := s.postWaypointWithToken(tok, destID, true, false); err != nil && firstErr == nil { firstErr = err } + } + } + return firstErr } \ No newline at end of file diff --git a/frontend/src/components/RegionMap.tsx b/frontend/src/components/RegionMap.tsx index 39dfba6..8363140 100644 --- a/frontend/src/components/RegionMap.tsx +++ b/frontend/src/components/RegionMap.tsx @@ -8,7 +8,7 @@ import { loadWormholeSystems, saveWormholeSystem, deleteWormholeSystem } from '@ import { System, Position, Connection as ConnectionType } from '@/lib/types'; import { getSecurityColor } from '@/utils/securityColors'; import { Header } from './Header'; -import { ListCharacters, StartESILogin, SetDestinationForAll, AddWaypointForAllByName } from 'wailsjs/go/main/App'; +import { ListCharacters, StartESILogin, SetDestinationForAll, AddWaypointForAllByName, PostRouteForAllByNames } from 'wailsjs/go/main/App'; import { toast } from '@/hooks/use-toast'; interface RegionMapProps { @@ -76,14 +76,10 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho useEffect(() => { const onKeyDown = async (e: KeyboardEvent) => { if (e.key === 'Escape' && viaMode) { - // Commit the route: post destination then queued waypoints try { if (!(await ensureAnyLoggedIn())) return; if (viaDest) { - await SetDestinationForAll(viaDest, true, false); - for (const name of viaQueue) { - await AddWaypointForAllByName(name, false); - } + await PostRouteForAllByNames(viaDest, viaQueue); toast({ title: 'Route set', description: `${viaDest}${viaQueue.length ? ' via ' + viaQueue.join(', ') : ''}` }); } } catch (err: any) { diff --git a/frontend/wailsjs/go/main/App.d.ts b/frontend/wailsjs/go/main/App.d.ts index 8db8af8..a3f24ec 100644 --- a/frontend/wailsjs/go/main/App.d.ts +++ b/frontend/wailsjs/go/main/App.d.ts @@ -12,9 +12,7 @@ export function Greet(arg1:string):Promise; export function ListCharacters():Promise>; -export function SetDestination(arg1:number,arg2:boolean,arg3:boolean):Promise; - -export function SetDestinationByName(arg1:string,arg2:boolean,arg3:boolean):Promise; +export function PostRouteForAllByNames(arg1:string,arg2:Array):Promise; export function SetDestinationForAll(arg1:string,arg2:boolean,arg3:boolean):Promise; diff --git a/frontend/wailsjs/go/main/App.js b/frontend/wailsjs/go/main/App.js index e75bc1d..6742dd8 100644 --- a/frontend/wailsjs/go/main/App.js +++ b/frontend/wailsjs/go/main/App.js @@ -22,12 +22,8 @@ export function ListCharacters() { return window['go']['main']['App']['ListCharacters'](); } -export function SetDestination(arg1, arg2, arg3) { - return window['go']['main']['App']['SetDestination'](arg1, arg2, arg3); -} - -export function SetDestinationByName(arg1, arg2, arg3) { - return window['go']['main']['App']['SetDestinationByName'](arg1, arg2, arg3); +export function PostRouteForAllByNames(arg1, arg2) { + return window['go']['main']['App']['PostRouteForAllByNames'](arg1, arg2); } export function SetDestinationForAll(arg1, arg2, arg3) {