clone

an offline map explorer where each world, from a game map to a city's open data, ships as one .atlas file

archive/golden-reference
atlas / main.go
main.go 4.1K
1// Command atlas is an offline interactive map explorer built with Allons.2// The executable carries only the application shell; each volume arrives as a3// self-contained .atlas bundle dropped into the bundles directory, and the4// application needs no network connection either way.5package main67import (8 "context"9 "embed"10 "fmt"11 "log/slog"12 "net/http"13 "os"14 "path/filepath"1516 "github.com/FelineStateMachine/allons/local"17 "github.com/FelineStateMachine/allons/local/events"18 "github.com/FelineStateMachine/allons/wailsapp"19 "github.com/FelineStateMachine/atlas/internal/bundle"20 "github.com/wailsapp/wails/v2/pkg/options"21)2223// catalogChangedTopic is the event the frontend refreshes its catalog on: a24// bundle arrived, was replaced, or left, and the volumes on offer moved with it.25const catalogChangedTopic = "atlas:catalog-changed"2627// Archive capture is deliberately not a generate step: it reaches out to28// MapGenie and takes as long as the chosen depth demands. Run it by hand for29// the game or map you want, then regenerate.30//31// go run ./tools/crawl -game skyrim -map solstheim -max-zoom 1532// node tools/icons/render-icons.mjs --game skyrim3334//go:generate go run ./tools/tiles -source ../gamemap/fmg-archive -output build/tiles35//go:generate go run ./tools/generate -source ../gamemap -tiles build/tiles/index.json36//go:generate npm --prefix frontend ci37//go:generate npm --prefix frontend run build3839//go:embed assets/index.html assets/app.css assets/app.js40var assets embed.FS4142func main() {43 // A development build asked to run headless serves the same application44 // over plain HTTP and opens no window: the parity sweep's whole ask.45 if runHeadless(assets) {46 return47 }48 // Created inside Routes, which the framework calls while opening the49 // application, and used again at startup below; Open completes before the50 // window exists, so the order holds.51 var registry *bundle.Registry52 err := wailsapp.Run(53 local.Config{54 AppID: "dev.felinestatemachine.atlas",55 Schema: local.Schema{AppSchemaVersion: 1, AppSchemaMinReadable: 1},56 Assets: assets,57 Bootstrap: local.CustomBootstrap("/"),58 Sync: local.SyncConfig{Autostart: local.AutostartOff},59 Routes: func(app *local.App) http.Handler {60 registry = bundle.NewRegistry(bundlesDir(app))61 if err := registry.Rescan(); err != nil {62 slog.Warn("atlas: scanning bundles", "error", err)63 }64 // Published through the bus so the frontend hears about the65 // library changing the same way it would hear about anything66 // else: the emitter is bound at startup, and events published67 // before a window exists have no one to reach anyway.68 registry.SetOnChange(func(changed []string) {69 app.Events().Publish(catalogChangedTopic, events.Detail{Entities: changed})70 })71 return routes(assets, registry)72 },73 },74 &options.App{75 Title: "Atlas — World Explorer",76 OnStartup: func(wctx context.Context) {77 captureWailsContext(wctx)78 if err := registry.Watch(wctx); err != nil {79 slog.Warn("atlas: watching bundles", "error", err)80 }81 },82 // A map wants every pixel it can have, so the window opens filling83 // the screen it lands on rather than at a size chosen here. Not84 // fullscreen: the menu bar and dock stay, and the window is still a85 // window. Width and Height are what it returns to when unzoomed.86 WindowStartState: options.Maximised,87 Width: 1440,88 Height: 920,89 MinWidth: 900,90 MinHeight: 600,91 },92 )93 if err != nil {94 fmt.Fprintln(os.Stderr, "atlas:", err)95 os.Exit(1)96 }97}9899// bundlesDir is where installed volumes live: a directory of .atlas files under100// the application's own data directory. ATLAS_BUNDLES_DIR points elsewhere for101// development, so a freshly generated dist/bundles serves without being copied102// into the running application's library.103func bundlesDir(app *local.App) string {104 dir := os.Getenv("ATLAS_BUNDLES_DIR")105 if dir == "" {106 dir = filepath.Join(app.DataDir(), "bundles")107 }108 if err := os.MkdirAll(dir, 0o755); err != nil {109 slog.Warn("atlas: creating bundles directory", "path", dir, "error", err)110 }111 return dir112}