- Added a new AGENTS.md file to document the project structure and conventions. - Updated .gitignore to include node_modules and refined cursor rules. - Introduced new backend and frontend components for improved map interactions, including context menus and controls. - Enhanced API composables for better admin and authentication functionalities. - Refactored existing components for cleaner code and improved user experience. - Updated README.md to clarify production asset serving and user setup instructions.
175 lines
4.2 KiB
Go
175 lines
4.2 KiB
Go
package services
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strconv"
|
|
|
|
"github.com/andyleap/hnh-map/internal/app"
|
|
"github.com/andyleap/hnh-map/internal/app/store"
|
|
"go.etcd.io/bbolt"
|
|
)
|
|
|
|
// MapService handles map, markers, grids, tiles business logic.
|
|
type MapService struct {
|
|
st *store.Store
|
|
gridStorage string
|
|
gridUpdates *app.Topic[app.TileData]
|
|
mergeUpdates *app.Topic[app.Merge]
|
|
getChars func() []app.Character
|
|
}
|
|
|
|
// MapServiceDeps holds dependencies for MapService.
|
|
type MapServiceDeps struct {
|
|
Store *store.Store
|
|
GridStorage string
|
|
GridUpdates *app.Topic[app.TileData]
|
|
MergeUpdates *app.Topic[app.Merge]
|
|
GetChars func() []app.Character
|
|
}
|
|
|
|
// NewMapService creates a MapService.
|
|
func NewMapService(d MapServiceDeps) *MapService {
|
|
return &MapService{
|
|
st: d.Store,
|
|
gridStorage: d.GridStorage,
|
|
gridUpdates: d.GridUpdates,
|
|
mergeUpdates: d.MergeUpdates,
|
|
getChars: d.GetChars,
|
|
}
|
|
}
|
|
|
|
// GridStorage returns the grid storage path.
|
|
func (s *MapService) GridStorage() string {
|
|
return s.gridStorage
|
|
}
|
|
|
|
// GetCharacters returns all characters (from in-memory map).
|
|
func (s *MapService) GetCharacters() []app.Character {
|
|
if s.getChars == nil {
|
|
return nil
|
|
}
|
|
return s.getChars()
|
|
}
|
|
|
|
// GetMarkers returns all markers as FrontendMarker list.
|
|
func (s *MapService) GetMarkers() ([]app.FrontendMarker, error) {
|
|
var markers []app.FrontendMarker
|
|
err := s.st.View(func(tx *bbolt.Tx) error {
|
|
grid := s.st.GetMarkersGridBucket(tx)
|
|
if grid == nil {
|
|
return nil
|
|
}
|
|
grids := tx.Bucket(store.BucketGrids)
|
|
if grids == nil {
|
|
return nil
|
|
}
|
|
return grid.ForEach(func(k, v []byte) error {
|
|
marker := app.Marker{}
|
|
json.Unmarshal(v, &marker)
|
|
graw := grids.Get([]byte(marker.GridID))
|
|
if graw == nil {
|
|
return nil
|
|
}
|
|
g := app.GridData{}
|
|
json.Unmarshal(graw, &g)
|
|
markers = append(markers, app.FrontendMarker{
|
|
Image: marker.Image,
|
|
Hidden: marker.Hidden,
|
|
ID: marker.ID,
|
|
Name: marker.Name,
|
|
Map: g.Map,
|
|
Position: app.Position{
|
|
X: marker.Position.X + g.Coord.X*100,
|
|
Y: marker.Position.Y + g.Coord.Y*100,
|
|
},
|
|
})
|
|
return nil
|
|
})
|
|
})
|
|
return markers, err
|
|
}
|
|
|
|
// GetMaps returns maps, optionally filtering hidden for non-admin.
|
|
func (s *MapService) GetMaps(showHidden bool) (map[int]*app.MapInfo, error) {
|
|
maps := make(map[int]*app.MapInfo)
|
|
err := s.st.View(func(tx *bbolt.Tx) error {
|
|
return s.st.ForEachMap(tx, func(k, v []byte) error {
|
|
mapid, err := strconv.Atoi(string(k))
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
mi := &app.MapInfo{}
|
|
json.Unmarshal(v, mi)
|
|
if mi.Hidden && !showHidden {
|
|
return nil
|
|
}
|
|
maps[mapid] = mi
|
|
return nil
|
|
})
|
|
})
|
|
return maps, err
|
|
}
|
|
|
|
// GetConfig returns config (title) and auths for session.
|
|
func (s *MapService) GetConfig(auths app.Auths) (app.Config, error) {
|
|
config := app.Config{Auths: auths}
|
|
err := s.st.View(func(tx *bbolt.Tx) error {
|
|
title := s.st.GetConfig(tx, "title")
|
|
if title != nil {
|
|
config.Title = string(title)
|
|
}
|
|
return nil
|
|
})
|
|
return config, err
|
|
}
|
|
|
|
// GetPage returns page title.
|
|
func (s *MapService) GetPage() (app.Page, error) {
|
|
p := app.Page{}
|
|
err := s.st.View(func(tx *bbolt.Tx) error {
|
|
title := s.st.GetConfig(tx, "title")
|
|
if title != nil {
|
|
p.Title = string(title)
|
|
}
|
|
return nil
|
|
})
|
|
return p, err
|
|
}
|
|
|
|
// GetGrid returns GridData by ID.
|
|
func (s *MapService) GetGrid(id string) (*app.GridData, error) {
|
|
var gd *app.GridData
|
|
err := s.st.View(func(tx *bbolt.Tx) error {
|
|
raw := s.st.GetGrid(tx, id)
|
|
if raw == nil {
|
|
return nil
|
|
}
|
|
gd = &app.GridData{}
|
|
return json.Unmarshal(raw, gd)
|
|
})
|
|
return gd, err
|
|
}
|
|
|
|
// GetTile returns TileData for map/zoom/coord.
|
|
func (s *MapService) GetTile(mapID int, c app.Coord, zoom int) *app.TileData {
|
|
var td *app.TileData
|
|
s.st.View(func(tx *bbolt.Tx) error {
|
|
raw := s.st.GetTile(tx, mapID, zoom, c.Name())
|
|
if raw != nil {
|
|
td = &app.TileData{}
|
|
json.Unmarshal(raw, td)
|
|
}
|
|
return nil
|
|
})
|
|
return td
|
|
}
|
|
|
|
// ReportMerge sends a merge event.
|
|
func (s *MapService) ReportMerge(from, to int, shift app.Coord) {
|
|
s.mergeUpdates.Send(&app.Merge{
|
|
From: from,
|
|
To: to,
|
|
Shift: shift,
|
|
})
|
|
}
|