Compare commits

...

4 Commits

Author SHA1 Message Date
Bardioc26 57ff19b8ee Char skill edit not saved (#36)
* Skill edit was not saved
* show warning when skill name is to be edited in char_skills
2026-02-27 11:59:30 +01:00
Bardioc26 261a6294cb Desktop app dynamic config of API Port (#40)
* added dynamic configuration of Port in Desktop app
* added dynamic configuration of API_URL to docker deployments.

Now it works with editing only .env file.
2026-02-27 11:55:30 +01:00
Bardioc26 bb9ef4f77e Added Desktop app deployment (#39)
Created a desktop app using Walis framework.
This embeds the frontend and backend into one binary that shows itself on a desktop
2026-02-24 22:10:05 +01:00
Frank c6539d17f4 Bump backend to 0.2.5, frontend to 0.2.4 2026-02-17 23:27:54 +01:00
43 changed files with 1753 additions and 85 deletions
+174
View File
@@ -0,0 +1,174 @@
# Runtime Configuration Implementation Summary
## What Was Implemented
Enhanced the BaMoRT frontend to support runtime configuration for **both desktop (Wails) and web production** deployments, eliminating the need to rebuild when changing API URLs.
## Architecture
### Desktop (Wails)
- Uses Go bindings to read API URL from `.env` file at runtime
- Method: `window['go']['main']['App']['GetAPIBaseURL']()`
- Configuration: `desktop/.env``API_PORT` variable
- No rebuild needed when changing port
### Web Production (NEW)
Uses a **multi-strategy fallback system**:
1. **config.json** (Priority 1)
- Loads `/config.json` from web root
- Can be modified after deployment
- Example: `{"apiBaseURL": "https://api.yourdomain.com"}`
2. **Auto-detection** (Priority 2)
- Probes `/api/public/version` at same origin
- Works automatically with reverse proxy setups
- Detects if backend and frontend share same domain
3. **VITE_API_URL** (Priority 3)
- Environment variable at build time
- Used for development: `VITE_API_URL=http://localhost:8180`
4. **Same Origin Fallback** (Priority 4)
- Uses `window.location.origin`
- Assumes backend and frontend on same domain
## Files Modified
### Core Implementation
- `/data/dev/bamort/frontend/src/utils/config.js` - Enhanced with multi-strategy config loading
- `/data/dev/bamort/frontend/src/utils/api.js` - Uses dynamic baseURL (already done for desktop)
- `/data/dev/bamort/desktop/main.go` - GetAPIBaseURL() Go binding (already done for desktop)
### Documentation
- `/data/dev/bamort/frontend/RUNTIME_CONFIG.md` - Complete web configuration guide
- `/data/dev/bamort/desktop/RUNTIME_CONFIG.md` - Desktop configuration guide (existing)
### Configuration Files
- `/data/dev/bamort/frontend/public/config.json.example` - Template for deployment
- `/data/dev/bamort/frontend/.gitignore` - Excludes `public/config.json` from git
## How It Works
### For Web Development
```bash
cd frontend
VITE_API_URL=http://localhost:8180 npm run dev
```
### For Web Production Deployment
**Option A: Using config.json (Recommended)**
```bash
# 1. Build once
cd frontend && npm run build
# 2. Deploy dist/ to web server
# 3. Create config.json in web root
cat > /var/www/bamort/config.json <<EOF
{
"apiBaseURL": "https://api.production.com"
}
EOF
# 4. Change API URL anytime without rebuild!
```
**Option B: Reverse Proxy Setup**
Example nginx configuration:
```nginx
server {
listen 80;
server_name yourdomain.com;
# Frontend
location / {
root /var/www/bamort;
try_files $uri $uri/ /index.html;
}
# Backend API
location /api {
proxy_pass http://localhost:8180;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
```
Frontend auto-detects and uses same origin.
**Option C: Build with Embedded URL**
```bash
VITE_API_URL=https://api.production.com npm run build
```
(Not recommended - requires rebuild for URL changes)
### For Desktop (Wails)
```bash
# 1. Build once
cd desktop && wails build
# 2. Change port in .env (no rebuild!)
echo "API_PORT=9000" > desktop/.env
# 3. Run app - uses new port automatically
./desktop/build/bin/bamort
```
## Verification
Open browser console when loading the app. Look for one of these messages:
**Desktop:**
```
Desktop app using API URL from config: http://localhost:8185
```
**Web:**
```
Loaded API URL from config.json: https://api.yourdomain.com
```
or
```
Detected backend at same origin: https://yourdomain.com
```
or
```
Web app using VITE_API_URL: http://localhost:8180
```
or
```
Web app using same origin: https://yourdomain.com
```
## Benefits
**No rebuild needed** for API URL changes in production web deployments
**Same build artifact** can be deployed to dev/staging/production
**Infrastructure-friendly** - works with reverse proxies, Docker, static hosting
**Flexible** - multiple configuration strategies with sensible fallbacks
**Dev-friendly** - VITE_API_URL still works for development
**Desktop-friendly** - reads .env at runtime (already implemented)
## Testing Checklist
- [x] Frontend builds successfully (`npm run build`)
- [x] Desktop app reads runtime config from .env
- [ ] Web dev mode works with VITE_API_URL
- [ ] Web production with config.json works
- [ ] Web production with reverse proxy auto-detection works
- [ ] Web production with same-origin fallback works
## Migration Path
**For existing deployments:**
1. No changes needed! Current builds continue working
2. To enable runtime config: Just add `config.json` to your web root
3. Old builds with VITE_API_URL still work as fallback
**For new deployments:**
1. Build once: `npm run build`
2. Deploy `dist/` contents
3. Add `config.json` with your API URL
4. Done!
+1 -1
View File
@@ -5,7 +5,7 @@ import (
) )
// Version is the application version // Version is the application version
const Version = "0.2.4" const Version = "0.2.5"
var ( var (
// GitCommit will be set by build flags or detected at runtime // GitCommit will be set by build flags or detected at runtime
+11 -1
View File
@@ -147,7 +147,17 @@ func UpdateCharacter(c *gin.Context) {
return return
} }
c.JSON(http.StatusOK, character) // Reload character to get updated data
var updatedCharacter models.Char
err = updatedCharacter.FirstID(id)
if err != nil {
respondWithError(c, http.StatusInternalServerError, "Failed to reload character")
return
}
// Return as FeChar with categorized skills
feChar := ToFeChar(&updatedCharacter)
c.JSON(http.StatusOK, feChar)
} }
func DeleteCharacter(c *gin.Context) { func DeleteCharacter(c *gin.Context) {
id := c.Param("id") id := c.Param("id")
+22 -8
View File
@@ -8,23 +8,37 @@ import (
) )
func SetupGin(r *gin.Engine) { func SetupGin(r *gin.Engine) {
// Build allowed origins list from configuration var corsConfig cors.Config
// Desktop: the Wails WebView origin varies by platform/version (wails.localhost,
// localhost, null …). Since the server is local, allow all origins.
if config.Cfg.Environment == "desktop" {
corsConfig = cors.Config{
AllowOriginFunc: func(_ string) bool { return true },
AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
MaxAge: 12 * 3600,
}
} else {
allowedOrigins := []string{ allowedOrigins := []string{
config.Cfg.FrontendURL, config.Cfg.FrontendURL,
"http://localhost:5173", // Development frontend "http://localhost:5173", // Development frontend
"http://192.168.0.48:5173", // Development frontend "http://192.168.0.48:5173", // Development frontend
"http://192.168.0.36:5173", // Development frontend "http://192.168.0.36:5173", // Development frontend
"https://bamort.trokan.de", // Production frontend "https://bamort.trokan.de", // Production frontend
"http://wails.localhost", // Wails desktop WebView
} }
corsConfig = cors.Config{
// Add CORS middleware
r.Use(cors.New(cors.Config{
//AllowOrigins: []string{"http://localhost:3000"}, // Replace with your frontend's URL
AllowOrigins: allowedOrigins, AllowOrigins: allowedOrigins,
AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"}, AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"}, AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
ExposeHeaders: []string{"Content-Length"}, ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true, AllowCredentials: true,
MaxAge: 12 * 3600, // Cache preflight for 12 hours MaxAge: 12 * 3600,
})) }
}
r.Use(cors.New(corsConfig))
} }
+6
View File
@@ -0,0 +1,6 @@
# Generated by `wails build` do not commit built artefacts
frontend/dist/*
!frontend/dist/.gitkeep
# Wails build outputs
build/bin/
+24
View File
@@ -0,0 +1,24 @@
# Desktop App for BaMoRT
To use it as a local Desktop App we packaged it into Wails.
Frontend and Backend are still the same code but the database, for now, is limited to SQLite
# How to build
preinstall
in Debian 13
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev
## Install Wails CLI once
go install github.com/wailsapp/wails/v2/cmd/wails@latest
## Development (Wails proxies the Vite dev server, run Vite separately first)
cd desktop && wails dev
## Production desktop binary
cd desktop && wails build
## Output: desktop/build/bin/bamort
+64
View File
@@ -0,0 +1,64 @@
# Desktop Runtime Configuration
The BaMoRT desktop app now uses **runtime configuration** for the API port, meaning you can change the port in the `.env` file without rebuilding the app.
## How It Works
### Backend (Go)
- **[desktop/main.go](desktop/main.go)**: The `App.GetAPIBaseURL()` method reads the configured port from `config.Cfg` and returns the full API URL
- This method is bound to the frontend via Wails, making it callable from JavaScript
### Frontend (Vue/JS)
- **[frontend/src/utils/config.js](frontend/src/utils/config.js)**: Detects if running in Wails and calls the Go backend to get the API URL at runtime
- **[frontend/src/utils/api.js](frontend/src/utils/api.js)**: Uses the dynamic config instead of a hardcoded VITE_API_URL
- The API URL is cached after first retrieval for performance
### Build Process
- **[frontend/package.json](frontend/package.json)**: The `build:desktop` script no longer hardcodes `VITE_API_URL`
- The frontend bundle is now port-agnostic
## Usage
### Change the API Port
1. Edit `desktop/.env`:
```env
API_PORT=8185 # Change to any port you want
```
2. Run the app - no rebuild needed!
```bash
./desktop/build/bin/bamort
```
The frontend will automatically connect to the configured port.
### Environment Detection
The config system automatically detects the environment:
- **Desktop (Wails)**: Calls `window.go.main.App.GetAPIBaseURL()` to get the URL from Go backend
- **Web (Development)**: Uses `import.meta.env.VITE_API_URL` from Vite
- **Web (Production)**: Defaults to `https://bamort-api.trokan.de`
## Fallback Behavior
If the desktop app can't reach the Go backend (shouldn't happen), it falls back to `http://localhost:8185`.
## Benefits
✅ Change API port without rebuilding
✅ Faster iteration during development
✅ Same binary works with different configurations
✅ Cleaner separation of config from code
## Technical Details
**Request Flow:**
1. Frontend makes API request
2. Axios interceptor checks if `baseURL` is set
3. If not set, calls `getAPIBaseURL()` from config.js
4. Config.js detects Wails environment via `window.go`
5. Calls Go backend's `GetAPIBaseURL()` method
6. Caches the result for subsequent requests
7. Request proceeds with correct baseURL
View File
+86
View File
@@ -0,0 +1,86 @@
module bamort-desktop
go 1.24.0
require (
bamort v0.0.0
github.com/gin-gonic/gin v1.10.0
github.com/wailsapp/wails/v2 v2.11.0
)
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/bep/debounce v1.2.1 // indirect
github.com/bytedance/sonic v1.12.8 // indirect
github.com/bytedance/sonic/loader v0.2.3 // indirect
github.com/chromedp/cdproto v0.0.0-20250803210736-d308e07a266d // indirect
github.com/chromedp/chromedp v0.14.2 // indirect
github.com/chromedp/sysutil v1.1.0 // indirect
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gin-contrib/cors v1.7.3 // indirect
github.com/gin-contrib/sse v1.0.0 // indirect
github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.24.0 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.4.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/google/go-cmp v0.5.8 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/hhrutter/lzw v1.0.0 // indirect
github.com/hhrutter/pkcs7 v0.2.0 // indirect
github.com/hhrutter/tiff v1.0.2 // indirect
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/labstack/echo/v4 v4.13.3 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
github.com/leaanthony/gosod v1.0.4 // indirect
github.com/leaanthony/slicer v1.6.0 // indirect
github.com/leaanthony/u v1.1.1 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/mattn/go-sqlite3 v1.14.24 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pdfcpu/pdfcpu v0.11.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/samber/lo v1.49.1 // indirect
github.com/tkrajina/go-reflector v0.5.8 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/wailsapp/go-webview2 v1.0.22 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
golang.org/x/arch v0.13.0 // indirect
golang.org/x/crypto v0.43.0 // indirect
golang.org/x/image v0.32.0 // indirect
golang.org/x/net v0.45.0 // indirect
golang.org/x/sys v0.37.0 // indirect
golang.org/x/text v0.30.0 // indirect
google.golang.org/protobuf v1.36.4 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gorm.io/driver/mysql v1.5.7 // indirect
gorm.io/driver/sqlite v1.5.7 // indirect
gorm.io/gorm v1.25.12 // indirect
)
replace bamort => ../backend
+207
View File
@@ -0,0 +1,207 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
github.com/bytedance/sonic v1.12.8 h1:4xYRVRlXIgvSZ4e8iVTlMF5szgpXd4AfvuWgA8I8lgs=
github.com/bytedance/sonic v1.12.8/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0=
github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/chromedp/cdproto v0.0.0-20250803210736-d308e07a266d h1:ZtA1sedVbEW7EW80Iz2GR3Ye6PwbJAJXjv7D74xG6HU=
github.com/chromedp/cdproto v0.0.0-20250803210736-d308e07a266d/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k=
github.com/chromedp/chromedp v0.14.2 h1:r3b/WtwM50RsBZHMUm9fsNhhzRStTHrKdr2zmwbZSzM=
github.com/chromedp/chromedp v0.14.2/go.mod h1:rHzAv60xDE7VNy/MYtTUrYreSc0ujt2O1/C3bzctYBo=
github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM=
github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8=
github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gin-contrib/cors v1.7.3 h1:hV+a5xp8hwJoTw7OY+a70FsL8JkVVFTXw9EcfrYUdns=
github.com/gin-contrib/cors v1.7.3/go.mod h1:M3bcKZhxzsvI+rlRSkkxHyljJt1ESd93COUvemZ79j4=
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2 h1:iizUGZ9pEquQS5jTGkh4AqeeHCMbfbjeb0zMt0aEFzs=
github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hhrutter/lzw v1.0.0 h1:laL89Llp86W3rRs83LvKbwYRx6INE8gDn0XNb1oXtm0=
github.com/hhrutter/lzw v1.0.0/go.mod h1:2HC6DJSn/n6iAZfgM3Pg+cP1KxeWc3ezG8bBqW5+WEo=
github.com/hhrutter/pkcs7 v0.2.0 h1:i4HN2XMbGQpZRnKBLsUwO3dSckzgX142TNqY/KfXg+I=
github.com/hhrutter/pkcs7 v0.2.0/go.mod h1:aEzKz0+ZAlz7YaEMY47jDHL14hVWD6iXt0AgqgAvWgE=
github.com/hhrutter/tiff v1.0.2 h1:7H3FQQpKu/i5WaSChoD1nnJbGx4MxU5TlNqqpxw55z8=
github.com/hhrutter/tiff v1.0.2/go.mod h1:pcOeuK5loFUE7Y/WnzGw20YxUdnqjY1P0Jlcieb/cCw=
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY=
github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc=
github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A=
github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
github.com/leaanthony/gosod v1.0.4 h1:YLAbVyd591MRffDgxUOU1NwLhT9T1/YiwjKZpkNFeaI=
github.com/leaanthony/gosod v1.0.4/go.mod h1:GKuIL0zzPj3O1SdWQOdgURSuhkF+Urizzxh26t9f1cw=
github.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/Js=
github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8=
github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M=
github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/pdfcpu/pdfcpu v0.11.1 h1:htHBSkGH5jMKWC6e0sihBFbcKZ8vG1M67c8/dJxhjas=
github.com/pdfcpu/pdfcpu v0.11.1/go.mod h1:pP3aGga7pRvwFWAm9WwFvo+V68DfANi9kxSQYioNYcw=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo3DLVQQ=
github.com/tkrajina/go-reflector v0.5.8/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/wailsapp/go-webview2 v1.0.22 h1:YT61F5lj+GGaat5OB96Aa3b4QA+mybD0Ggq6NZijQ58=
github.com/wailsapp/go-webview2 v1.0.22/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
github.com/wailsapp/wails/v2 v2.11.0 h1:seLacV8pqupq32IjS4Y7V8ucab0WZwtK6VvUVxSBtqQ=
github.com/wailsapp/wails/v2 v2.11.0/go.mod h1:jrf0ZaM6+GBc1wRmXsM8cIvzlg0karYin3erahI4+0k=
golang.org/x/arch v0.13.0 h1:KCkqVVV1kGg0X87TFysjCJ8MxtZEIU4Ja/yXGeoECdA=
golang.org/x/arch v0.13.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/image v0.32.0 h1:6lZQWq75h7L5IWNk0r+SCpUJ6tUVd3v4ZHnbRKLkUDQ=
golang.org/x/image v0.32.0/go.mod h1:/R37rrQmKXtO6tYXAjtDLwQgFLHmhW+V6ayXlxzP2Pc=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
+177
View File
@@ -0,0 +1,177 @@
// BaMoRT Desktop application entry point.
// Uses Wails v2 to wrap the existing Gin HTTP server in a native desktop window.
// The backend runs on localhost:8185 (SQLite); the WebView loads the bundled frontend.
package main
import (
"context"
"embed"
"io/fs"
"log"
"os"
"time"
"bamort/appsystem"
"bamort/character"
"bamort/config"
"bamort/database"
"bamort/equipment"
"bamort/gamesystem"
"bamort/gsmaster"
"bamort/importer"
"bamort/logger"
"bamort/maintenance"
"bamort/models"
"bamort/pdfrender"
"bamort/router"
"bamort/transfer"
"bamort/user"
"github.com/gin-gonic/gin"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)
// frontend/dist is populated by `wails build` running the frontend:build command
// in wails.json (outputs to ../desktop/frontend/dist via DESKTOP_BUILD=1).
//
//go:embed all:frontend/dist
var assets embed.FS
// App is the Wails application struct. Lifecycle hooks are attached to it.
type App struct {
ctx context.Context
}
// NewApp creates the Wails application instance.
func NewApp() *App {
return &App{}
}
// startup is called by Wails just before the WebView window is shown.
// It starts the Gin HTTP server in the background so the frontend can reach it.
func (a *App) startup(ctx context.Context) {
a.ctx = ctx
startGinServer()
}
// GetAPIBaseURL returns the HTTP server address for the frontend to connect to.
// This allows the API port to be configured at runtime via .env without rebuilding.
func (a *App) GetAPIBaseURL() string {
// GetServerAddress returns ":port", so we need to add localhost
return "http://localhost" + config.Cfg.GetServerAddress()
}
// startGinServer initialises the database, runs migrations, and starts the
// Gin HTTP server on the configured port (default 8185) in a goroutine.
func startGinServer() {
cfg := config.Cfg
logger.Info("BaMoRT Desktop Server wird gestartet...")
logger.Info("Environment: %s", cfg.Environment)
logger.Info("Database: %s @ %s", cfg.DatabaseType, cfg.DatabaseURL)
gin.SetMode(gin.ReleaseMode)
// Connect to SQLite database.
database.ConnectDatabase()
// Auto-migrate all modules so a fresh SQLite file gets a complete schema.
if err := models.MigrateStructure(); err != nil {
logger.Warn("models migration: %s", err.Error())
}
if err := user.MigrateStructure(); err != nil {
logger.Warn("user migration: %s", err.Error())
}
if err := gamesystem.MigrateStructure(); err != nil {
logger.Warn("gamesystem migration: %s", err.Error())
}
if err := database.MigrateStructure(); err != nil {
logger.Warn("database migration: %s", err.Error())
}
// Initialise PDF templates (source from ./default_templates if present).
if err := pdfrender.InitializeTemplates("./default_templates", cfg.TemplatesDir); err != nil {
logger.Warn("template init: %s", err.Error())
}
r := gin.Default()
router.SetupGin(r)
protected := router.BaseRouterGrp(r)
user.RegisterRoutes(protected)
gsmaster.RegisterRoutes(protected)
character.RegisterRoutes(protected)
equipment.RegisterRoutes(protected)
maintenance.RegisterRoutes(protected)
importer.RegisterRoutes(protected)
pdfrender.RegisterRoutes(protected)
transfer.RegisterRoutes(protected)
appsystem.RegisterRoutes(protected)
pdfrender.RegisterPublicRoutes(r)
appsystem.RegisterPublicRoutes(r)
logger.Info("Desktop API server starting on %s", cfg.GetServerAddress())
go func() {
if err := r.Run(cfg.GetServerAddress()); err != nil {
log.Printf("Gin server error: %v", err)
}
}()
// Brief pause to let the HTTP server bind before the WebView starts loading.
time.Sleep(150 * time.Millisecond)
}
func main() {
// Apply desktop defaults BEFORE anything uses config.Cfg.
// config.init() already ran (at import time) and may have picked up .env;
// here we ensure the critical SQLite settings exist even without a .env file.
if os.Getenv("DATABASE_TYPE") == "" {
_ = os.Setenv("DATABASE_TYPE", "sqlite")
}
if os.Getenv("DATABASE_URL") == "" {
_ = os.Setenv("DATABASE_URL", "./bamort-desktop.db")
}
if os.Getenv("ENVIRONMENT") == "" {
_ = os.Setenv("ENVIRONMENT", "desktop")
}
if os.Getenv("API_PORT") == "" {
_ = os.Setenv("API_PORT", "8185")
}
if os.Getenv("TEMPLATES_DIR") == "" {
_ = os.Setenv("TEMPLATES_DIR", "./templates")
}
if os.Getenv("EXPORT_TEMP_DIR") == "" {
_ = os.Setenv("EXPORT_TEMP_DIR", "./export_temp")
}
// Reload config so the above env vars take effect (init() may have run first).
config.Cfg = config.LoadConfig()
// Sub-FS rooted at frontend/dist so Wails serves it from /.
frontendFS, err := fs.Sub(assets, "frontend/dist")
if err != nil {
log.Fatal("frontend/dist embed error:", err)
}
app := NewApp()
if err := wails.Run(&options.App{
Title: "BaMoRT",
Width: 1400,
Height: 900,
MinWidth: 1024,
MinHeight: 768,
OnStartup: app.startup,
Bind: []interface{}{
app,
},
AssetServer: &assetserver.Options{
Assets: frontendFS,
},
}); err != nil {
log.Fatal(err)
}
}
+14
View File
@@ -0,0 +1,14 @@
{
"$schema": "https://wails.io/schemas/config.v2.json",
"name": "bamort",
"outputfilename": "bamort",
"frontend:dir": "../frontend",
"frontend:install": "npm install",
"frontend:build": "npm run build:desktop",
"frontend:dev:watcher": "npm run dev",
"frontend:dev:serverUrl": "http://localhost:5173",
"build:tags": "webkit2_41",
"author": {
"name": "Bamort"
}
}
+5 -5
View File
@@ -1,9 +1,5 @@
# Environment variables for Bamort development environment # Environment variables for Bamort development environment
# API Configuration
# API_URL=http://localhost:8180
# Database Configuration (for development)
DATABASE_TYPE=mysql DATABASE_TYPE=mysql
DATABASE_URL=bamort:bG4)efozrc@tcp(mariadb-dev:3306)/bamort?charset=utf8mb4&parseTime=True&loc=Local DATABASE_URL=bamort:bG4)efozrc@tcp(mariadb-dev:3306)/bamort?charset=utf8mb4&parseTime=True&loc=Local
@@ -13,9 +9,13 @@ MARIADB_PASSWORD=bG4)efozrc
MARIADB_DATABASE=bamort MARIADB_DATABASE=bamort
MARIADB_USER=bamort MARIADB_USER=bamort
# Frontend Configuration # API Configuration
# API_URL is used by the frontend to connect to the backend
# For dev environment with Vite dev server, this goes to VITE_API_URL
API_URL=http://192.168.0.48:8180 API_URL=http://192.168.0.48:8180
API_PORT=8180 API_PORT=8180
# Frontend Configuration
BASE_URL=http://localhost:5173 BASE_URL=http://localhost:5173
TEMPLATES_DIR=./templates TEMPLATES_DIR=./templates
EXPORT_TEMP_DIR=./export_temp EXPORT_TEMP_DIR=./export_temp
+16 -3
View File
@@ -1,9 +1,16 @@
# Environment variables for Bamort production deployment # Environment variables for Bamort production deployment
# Copy this file to .env and adjust the values as needed # Copy this file to .env.dev or .env.prd and adjust the values as needed
#- API Configuration # ===== FRONTEND API CONFIGURATION =====
# API_URL: The backend API endpoint that the frontend will use
# For production: Use your public API domain (https://api.yourdomain.com)
# For development: Use your local network IP (http://192.168.x.x:8180)
#
# IMPORTANT: After changing API_URL, just restart containers - NO REBUILD NEEDED!
# Production: docker-compose -f docker-compose.yml restart frontend
# Development: docker-compose -f docker-compose.dev.yml restart frontend-dev
API_URL=https://backend.domain.de API_URL=https://backend.domain.de
VITE_API_URL=https://backend.domain.de API_PORT=8180
#- Database Configuration Backend #- Database Configuration Backend
DATABASE_TYPE=mysql DATABASE_TYPE=mysql
@@ -20,3 +27,9 @@ BASE_URL=https://frontend.domain.de
TEMPLATES_DIR=./templates TEMPLATES_DIR=./templates
EXPORT_TEMP_DIR=./export_temp EXPORT_TEMP_DIR=./export_temp
COMPOSE_PROJECT_NAME=bamort COMPOSE_PROJECT_NAME=bamort
# Mail Configuration (for development)
MAIL_HOST=mail.server.de
MAIL_PORT=465
MAIL_USERNAME=bamort@server.de
MAIL_PASSWORD=XXXZZZZZ.YYYXXX
+4 -4
View File
@@ -1,10 +1,10 @@
# Environment variables for Bamort production environment # Environment variables for Bamort production environment
# API Configuration # API Configuration
# API_URL is used by the frontend to generate config.json at container startup
# Change this value and restart containers (no rebuild needed!)
API_URL=https://bamort-api.trokan.de API_URL=https://bamort-api.trokan.de
VITE_API_URL=https://bamort-api.trokan.de API_PORT=8180
# Database Configuration Backend
DATABASE_TYPE=mysql DATABASE_TYPE=mysql
#DATABASE_URL=bamort:bG4)efozrc@tcp(mariadb:3306)/bamort?charset=utf8mb4&parseTime=True&loc=Local #DATABASE_URL=bamort:bG4)efozrc@tcp(mariadb:3306)/bamort?charset=utf8mb4&parseTime=True&loc=Local
@@ -14,7 +14,7 @@ MARIADB_PASSWORD=bG4)efozrc
MARIADB_DATABASE=bamort MARIADB_DATABASE=bamort
MARIADB_USER=bamort MARIADB_USER=bamort
API_PORT=8180 # Frontend Configuration
BASE_URL=https://bamort.trokan.de BASE_URL=https://bamort.trokan.de
TEMPLATES_DIR=./templates TEMPLATES_DIR=./templates
EXPORT_TEMP_DIR=./export_temp EXPORT_TEMP_DIR=./export_temp
+12 -17
View File
@@ -1,42 +1,37 @@
# =========== 1) Build stage =========== # =========== 1) Build stage ===========
FROM node:22-alpine AS build FROM node:22-alpine AS build
# Accept build arguments for Vite environment variables # No build args needed - using runtime configuration instead
ARG VITE_API_URL
ARG VITE_BASE_URL
ARG VITE_API_PORT
# Set them as environment variables for the build process
ENV VITE_API_URL=$VITE_API_URL
ENV VITE_BASE_URL=$VITE_BASE_URL
ENV VITE_API_PORT=$VITE_API_PORT
WORKDIR /usr/src/app WORKDIR /usr/src/app
# Copy package files # Copy package files
COPY package*.json ./ COPY package*.json ./
# Install dependencies # Install dependencies
RUN npm install RUN npm install
# Copy the rest of the frontend code # Copy the rest of the frontend code
COPY . . COPY . .
# Build the production bundle # Build the production bundle WITHOUT baked-in API URL
# Runtime configuration will be generated from environment variables
RUN npm run build RUN npm run build
# =========== 2) Serve stage =========== # =========== 2) Serve stage ===========
FROM nginx:alpine FROM nginx:alpine
# Copy production build to Nginx html folder. # Copy production build to Nginx html folder
# Adjust /usr/src/app/build -> /usr/src/app/dist if youre using Angular/Vue
#COPY --from=build /usr/src/app/build /usr/share/nginx/html
COPY --from=build /usr/src/app/dist /usr/share/nginx/html COPY --from=build /usr/src/app/dist /usr/share/nginx/html
# Copy custom nginx configuration for SPA routing # Copy custom nginx configuration for SPA routing
COPY --from=build /usr/src/app/nginx.conf /etc/nginx/conf.d/default.conf COPY --from=build /usr/src/app/nginx.conf /etc/nginx/conf.d/default.conf
# Copy entrypoint script that generates runtime config
COPY --from=build /usr/src/app/docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh
# Expose HTTP port # Expose HTTP port
EXPOSE 80 EXPOSE 80
# Run Nginx in foreground # Use custom entrypoint that generates config.json from environment
CMD ["nginx", "-g", "daemon off;"] ENTRYPOINT ["/docker-entrypoint.sh"]
+6 -2
View File
@@ -9,8 +9,12 @@ COPY package*.json ./
# Install dependencies # Install dependencies
RUN npm install RUN npm install
# Copy entrypoint script that generates config.json
COPY docker-dev-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh
# Expose Vite dev server port # Expose Vite dev server port
EXPOSE 5173 EXPOSE 5173
# Start development server with host binding for Docker # Use entrypoint that generates config.json before starting Vite
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"] ENTRYPOINT ["/docker-entrypoint.sh"]
+1 -7
View File
@@ -25,18 +25,12 @@ services:
build: build:
context: ../frontend context: ../frontend
dockerfile: ../docker/Dockerfile.frontend dockerfile: ../docker/Dockerfile.frontend
args:
VITE_API_URL: ${API_URL:-https://bamort-api.trokan.de}
VITE_BASE_URL: ${BASE_URL:-https://bamort.trokan.de}
VITE_API_PORT: ${API_PORT:-443}
container_name: bamort-frontend container_name: bamort-frontend
ports: ports:
- "8181:80" - "8181:80"
environment: environment:
- NODE_ENV=production - NODE_ENV=production
- VITE_API_URL=${API_URL:-https://bamort.trokan.de:8180} - API_URL=${API_URL:-https://bamort-api.trokan.de}
- VITE_BASE_URL=${BASE_URL:-https://bamort.trokan.de}
- VITE_API_PORT=${API_PORT:-8180}
depends_on: depends_on:
- backend - backend
restart: unless-stopped restart: unless-stopped
+35
View File
@@ -0,0 +1,35 @@
#!/bin/sh
set -e
# Generate config.json from VITE_API_URL for development
# This makes the dev environment behave the same as production
CONFIG_FILE="/app/public/config.json"
echo "🔧 Generating development config.json..."
# Use VITE_API_URL from environment
API_BASE_URL="${VITE_API_URL:-}"
if [ -z "$API_BASE_URL" ]; then
echo "⚠️ VITE_API_URL not set, creating minimal config"
cat > "$CONFIG_FILE" <<EOF
{
"_comment": "Development mode - VITE_API_URL will be used as fallback"
}
EOF
else
echo "✅ API URL configured: $API_BASE_URL"
cat > "$CONFIG_FILE" <<EOF
{
"apiBaseURL": "$API_BASE_URL"
}
EOF
fi
echo "📄 Created config.json:"
cat "$CONFIG_FILE"
# Start Vite dev server
echo "🚀 Starting Vite dev server..."
exec npm run dev -- --host 0.0.0.0
+36
View File
@@ -0,0 +1,36 @@
#!/bin/sh
set -e
# Generate config.json from environment variables at container startup
# This allows runtime configuration without rebuilding the container
CONFIG_FILE="/usr/share/nginx/html/config.json"
echo "🔧 Generating frontend runtime configuration..."
# Use API_URL from environment, or fallback to same origin
API_BASE_URL="${API_URL:-}"
if [ -z "$API_BASE_URL" ]; then
echo "⚠️ API_URL not set, frontend will auto-detect or use same origin"
# Create minimal config that triggers auto-detection
cat > "$CONFIG_FILE" <<EOF
{
"_comment": "API_URL not configured, using auto-detection"
}
EOF
else
echo "✅ API URL configured: $API_BASE_URL"
cat > "$CONFIG_FILE" <<EOF
{
"apiBaseURL": "$API_BASE_URL"
}
EOF
fi
echo "📄 Generated config.json:"
cat "$CONFIG_FILE"
# Start nginx
echo "🚀 Starting nginx..."
exec nginx -g "daemon off;"
+18 -1
View File
@@ -11,16 +11,33 @@ fi
# Gehe ins Docker-Verzeichnis # Gehe ins Docker-Verzeichnis
cd "$(dirname "$0")" cd "$(dirname "$0")"
# Load development environment variables
if [ -f .env.dev ]; then
echo "📝 Loading configuration from .env.dev"
export $(grep -v '^#' .env.dev | xargs)
else
if [ -f .env ]; then
echo "📝 Loading configuration from .env"
export $(grep -v '^#' .env | xargs)
else
echo "⚠️ Warning: .env not found, using defaults"
fi
fi
# Get current git commit # Get current git commit
export GIT_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown") export GIT_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
echo "📝 Git Commit: $GIT_COMMIT" echo "📝 Git Commit: $GIT_COMMIT"
echo "📦 Building and starting development containers..." echo "📦 Building and starting development containers..."
echo "🔧 Frontend will use API: ${API_URL:-http://localhost:8180}"
# Stoppe vorhandene Container # Stoppe vorhandene Container
docker-compose -f docker-compose.dev.yml down docker-compose -f docker-compose.dev.yml down
# Baue und starte die Container # Baue und starte die Container
docker-compose -f docker-compose.dev.yml up --build -d docker-compose -f docker-compose.dev.yml --env-file .env.dev up --build -d
echo "✅ Development environment started." echo "✅ Development environment started."
echo "📱 Frontend: http://localhost:5173"
echo "🔌 Backend: http://localhost:8180"
echo "🗄️ phpMyAdmin: http://localhost:8081"
+23 -2
View File
@@ -11,14 +11,35 @@ fi
# Gehe ins Docker-Verzeichnis # Gehe ins Docker-Verzeichnis
cd "$(dirname "$0")" cd "$(dirname "$0")"
# Load production environment variables
if [ -f .env.prd ]; then
echo "📝 Loading configuration from .env.prd"
export $(grep -v '^#' .env.prd | xargs)
else
if [ -f .env ]; then
echo "📝 Loading configuration from .env"
export $(grep -v '^#' .env | xargs)
else
echo "⚠️ Warning: .env not found, using defaults"
fi
fi
echo "📦 Building and starting production containers..." echo "📦 Building and starting production containers..."
echo "🔧 Frontend will use API: ${API_URL:-https://bamort-api.trokan.de}"
# Build before stopping existing containers # Build before stopping existing containers
docker-compose -f docker-compose.yml build docker-compose -f docker-compose.yml --env-file .env.prd build
# Stoppe vorhandene Container # Stoppe vorhandene Container
docker-compose -f docker-compose.yml down docker-compose -f docker-compose.yml down
# Baue und starte die Container # Baue und starte die Container
docker-compose -f docker-compose.yml up --build -d docker-compose -f docker-compose.yml --env-file .env.prd up -d
echo "✅ Production environment started." echo "✅ Production environment started."
echo "📱 Frontend: http://localhost:8181"
echo "🔌 Backend: http://localhost:8182"
echo ""
echo "💡 To change API URL: Edit .env.prd and run:"
echo " docker-compose -f docker-compose.yml restart frontend"
echo " (No rebuild needed!)"
+7
View File
@@ -13,6 +13,13 @@ dist
dist-ssr dist-ssr
*.local *.local
# Runtime configuration (use config.json.example as template)
public/config.json
# Docker entrypoint scripts (copied from ../docker/ during build)
docker-entrypoint.sh
docker-dev-entrypoint.sh
# Editor directories and files # Editor directories and files
.vscode/* .vscode/*
!.vscode/extensions.json !.vscode/extensions.json
+1 -1
View File
@@ -1,6 +1,6 @@
# Frontend Version Management # Frontend Version Management
## Current Version: 0.2.3 ## Current Version: 0.2.4
The frontend version is managed independently from the backend. The frontend version is managed independently from the backend.
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "bamort-frontend", "name": "bamort-frontend",
"version": "0.1.29", "version": "0.2.4",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "bamort-frontend", "name": "bamort-frontend",
"version": "0.1.29", "version": "0.2.4",
"license": "SEE LICENSE IN LICENSE", "license": "SEE LICENSE IN LICENSE",
"dependencies": { "dependencies": {
"axios": "^1.7.9", "axios": "^1.7.9",
+2 -1
View File
@@ -1,12 +1,13 @@
{ {
"name": "bamort-frontend", "name": "bamort-frontend",
"version": "0.2.3", "version": "0.2.4",
"private": true, "private": true,
"license": "SEE LICENSE IN LICENSE", "license": "SEE LICENSE IN LICENSE",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",
"build:desktop": "DESKTOP_BUILD=1 vite build",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
+1
View File
@@ -0,0 +1 @@
705a38c2f133892600ec967917c4d556
+4
View File
@@ -0,0 +1,4 @@
{
"apiBaseURL": "http://localhost:8180",
"_comment": "This file can be modified at deployment without rebuilding. Copy to config.json and adjust apiBaseURL for your environment."
}
+7 -1
View File
@@ -32,7 +32,7 @@
<!-- Submenu Content --> <!-- Submenu Content -->
<!-- <div class="character-aspect"> --> <!-- <div class="character-aspect"> -->
<component :is="currentView" :character="character" :isOwner="isOwner" @character-updated="refreshCharacter"/> <component :is="currentView" :character="character" :isOwner="isOwner" @character-updated="refreshCharacter" @character-data-updated="updateCharacterData"/>
<!-- </div> --> <!-- </div> -->
<!-- Submenu --> <!-- Submenu -->
@@ -156,6 +156,12 @@ export default {
alert('Fehler beim Aktualisieren der Charakterdaten: ' + (error.response?.data?.error || error.message)); alert('Fehler beim Aktualisieren der Charakterdaten: ' + (error.response?.data?.error || error.message));
} }
}, },
updateCharacterData(updatedData) {
// Update character data directly without reloading from server
this.character = updatedData;
console.log('Character data updated directly from response');
},
}, },
}; };
</script> </script>
+75 -5
View File
@@ -55,6 +55,15 @@
</div> </div>
</div> </div>
</div> </div>
<!-- Warning message when editing skill name -->
<div v-if="showNameEditWarning" class="warning-message">
<span class="warning-icon"></span>
<span class="warning-text">
{{ $t('characters.datasheet.editnamewarning') }}
</span>
</div>
<table class="cd-table"> <table class="cd-table">
<thead> <thead>
<tr> <tr>
@@ -384,6 +393,29 @@
transform: translateX(0); transform: translateX(0);
} }
} }
.warning-message {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 16px;
margin: 10px 0;
background-color: #fff3cd;
border: 1px solid #ffc107;
border-radius: 6px;
color: #856404;
animation: slideIn 0.3s ease;
}
.warning-icon {
font-size: 1.2rem;
flex-shrink: 0;
}
.warning-text {
font-size: 0.9rem;
line-height: 1.4;
}
</style> </style>
<script> <script>
@@ -431,6 +463,7 @@ export default {
editingSkillId: null, editingSkillId: null,
editingField: null, editingField: null,
editValue: '', editValue: '',
showNameEditWarning: false,
isLoading: false isLoading: false
}; };
@@ -752,6 +785,11 @@ export default {
this.editingField = field; this.editingField = field;
this.editValue = skill[field] || ''; this.editValue = skill[field] || '';
// Show warning when editing skill name
if (field === 'name') {
this.showNameEditWarning = true;
}
this.$nextTick(() => { this.$nextTick(() => {
const input = this.$refs.editInput; const input = this.$refs.editInput;
if (input) { if (input) {
@@ -779,19 +817,50 @@ export default {
} }
} }
// Update local character object // Find and update the original skill in character.fertigkeiten or character.waffenfertigkeiten
skill[field] = newValue; let originalSkillFound = false;
// Check in fertigkeiten
if (this.character.fertigkeiten) {
const originalSkill = this.character.fertigkeiten.find(s => s.name === skill.name);
if (originalSkill) {
originalSkill[field] = newValue;
originalSkillFound = true;
}
}
// Check in waffenfertigkeiten if not found in fertigkeiten
if (!originalSkillFound && this.character.waffenfertigkeiten) {
const originalWeaponSkill = this.character.waffenfertigkeiten.find(s => s.name === skill.name);
if (originalWeaponSkill) {
originalWeaponSkill[field] = newValue;
originalSkillFound = true;
}
}
if (!originalSkillFound) {
console.warn('Original skill not found in character data:', skill.name);
alert('Warnung: Originalfertigkeit nicht gefunden.');
this.cancelEditSkill();
return;
}
try { try {
// Save to backend // Save to backend
await API.put(`/api/characters/${this.character.id}`, this.character); const response = await API.put(`/api/characters/${this.character.id}`, this.character);
console.log('Skill updated successfully:', skill.name, field, newValue);
// Update the parent component with the response data
// The backend now returns the full FeChar with categorizedskills
this.$emit('character-data-updated', response.data);
this.$emit('character-updated');
this.cancelEditSkill(); this.cancelEditSkill();
} catch (error) { } catch (error) {
console.error('Failed to update skill:', error); console.error('Failed to update skill:', error);
alert('Fehler beim Speichern: ' + (error.response?.data?.error || error.message)); alert('Fehler beim Speichern: ' + (error.response?.data?.error || error.message));
this.cancelEditSkill(); // Revert changes on error by reloading
this.$emit('character-updated');
} }
}, },
@@ -799,6 +868,7 @@ export default {
this.editingSkillId = null; this.editingSkillId = null;
this.editingField = null; this.editingField = null;
this.editValue = ''; this.editValue = '';
this.showNameEditWarning = false;
}, },
isEditingSkill(skill, field) { isEditingSkill(skill, field) {
+2 -1
View File
@@ -563,7 +563,8 @@ export default {
bRollTooltip: 'B würfeln: 1d6 + Modifikator je nach Rasse' bRollTooltip: 'B würfeln: 1d6 + Modifikator je nach Rasse'
}, },
datasheet: { datasheet: {
editHelp: 'Doppelklicken auf ein Feld, um es zu bearbeiten.' editHelp: 'Doppelklicken auf ein Feld, um es zu bearbeiten.',
editnamewarning: 'Wenn der Name der Fertigkeit geändert wird kann diese nicht mehr nach den Regeln verbessert werden. Auch beim Export können Boni und andere Werte fehlen!'
} }
}, },
audit: { audit: {
+2 -1
View File
@@ -559,7 +559,8 @@ export default {
bRollTooltip: 'Roll B: 1d6 + modifier based on race' bRollTooltip: 'Roll B: 1d6 + modifier based on race'
}, },
datasheet: { datasheet: {
editHelp: 'Click twice on a field to edit it.' editHelp: 'Click twice on a field to edit it.',
editnamewarning: 'If the name of the skill is changed, it can no longer be improved according to the rules. Also, bonuses and other values may be missing when exporting!'
} }
}, },
audit: { audit: {
+26 -5
View File
@@ -1,12 +1,33 @@
import axios from 'axios' import axios from 'axios'
import { getAPIBaseURL } from './config'
const API = axios.create({ // Create API instance without baseURL - will be set dynamically
baseURL: import.meta.env.VITE_API_URL || 'https://bamort-api.trokan.de', // Use env variable with fallback const API = axios.create({})
})
// Request interceptor to add auth token let baseURLPromise = null
let baseURLResolved = false
// Get base URL (cached after first call)
async function ensureBaseURL() {
if (baseURLResolved) {
return
}
if (!baseURLPromise) {
baseURLPromise = getAPIBaseURL()
}
const baseURL = await baseURLPromise
API.defaults.baseURL = baseURL
baseURLResolved = true
}
// Request interceptor to add auth token and ensure baseURL is set
API.interceptors.request.use( API.interceptors.request.use(
(config) => { async (config) => {
// Ensure baseURL is set before request
await ensureBaseURL()
const token = localStorage.getItem('token') const token = localStorage.getItem('token')
if (token) { if (token) {
config.headers.Authorization = `Bearer ${token}` config.headers.Authorization = `Bearer ${token}`
+134
View File
@@ -0,0 +1,134 @@
/**
* Runtime configuration for BaMoRT frontend
*
* For desktop builds (Wails), reads the API base URL from the Go backend
* to allow runtime configuration via .env without rebuilding.
*
* For web builds, uses runtime config.json or auto-detection:
* 1. Try to load /config.json (can be modified at deployment without rebuild)
* 2. Try to detect backend at same origin (production reverse proxy setup)
* 3. Fall back to VITE_API_URL (development) or same origin
*/
let cachedAPIBaseURL = null
/**
* Try to load configuration from /config.json
*/
async function loadConfigFile() {
try {
const response = await fetch('/config.json', {
cache: 'no-cache',
headers: { 'Accept': 'application/json' }
})
if (response.ok) {
// Check if response is actually JSON (not HTML from SPA fallback)
const contentType = response.headers.get('content-type')
if (contentType && contentType.includes('application/json')) {
const config = await response.json()
if (config.apiBaseURL) {
console.log('Loaded API URL from config.json:', config.apiBaseURL)
return config.apiBaseURL
}
}
}
} catch (error) {
// config.json doesn't exist or is invalid, that's okay
}
return null
}
/**
* Try to detect backend at same origin (production setup)
*/
async function detectBackendAtOrigin() {
try {
const origin = window.location.origin
const response = await fetch(`${origin}/api/public/version`, {
method: 'GET',
cache: 'no-cache',
signal: AbortSignal.timeout(2000) // 2 second timeout
})
if (response.ok) {
console.log('Detected backend at same origin:', origin)
return origin
}
} catch (error) {
// Backend not at same origin, that's okay
}
return null
}
/**
* Get the API base URL dynamically
* - In Wails desktop app: calls Go backend to get configured URL
* - In web app: tries config.json, auto-detection, or VITE_API_URL
*/
export async function getAPIBaseURL() {
// Return cached value if available
if (cachedAPIBaseURL) {
return cachedAPIBaseURL
}
// Try Wails desktop app first (with timeout)
if (typeof window !== 'undefined' && window['go']) {
// Wait up to 3 seconds for Wails bindings to be ready
for (let i = 0; i < 30; i++) {
try {
if (window['go']?.['main']?.['App']?.['GetAPIBaseURL']) {
const url = await window['go']['main']['App']['GetAPIBaseURL']()
cachedAPIBaseURL = url
console.log('Desktop app using API URL from config:', url)
return url
}
} catch (error) {
console.error('Failed to get API URL from Wails:', error)
break
}
await new Promise(resolve => setTimeout(resolve, 100))
}
// Wails detected but binding failed - use desktop fallback
cachedAPIBaseURL = 'http://localhost:8185'
console.log('Desktop app using fallback:', cachedAPIBaseURL)
return cachedAPIBaseURL
}
// Web app - try multiple strategies
// Strategy 1: Load from config.json (can be modified at deployment)
const configFileURL = await loadConfigFile()
if (configFileURL) {
cachedAPIBaseURL = configFileURL
return cachedAPIBaseURL
}
// Strategy 2: Check if backend is at same origin (production reverse proxy)
if (window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') {
const sameOriginURL = await detectBackendAtOrigin()
if (sameOriginURL) {
cachedAPIBaseURL = sameOriginURL
return cachedAPIBaseURL
}
}
// Strategy 3: Use VITE_API_URL (development) or fallback to same origin
if (import.meta.env.VITE_API_URL) {
cachedAPIBaseURL = import.meta.env.VITE_API_URL
console.log('Web app using VITE_API_URL:', cachedAPIBaseURL)
} else {
// Final fallback: assume same origin for production
cachedAPIBaseURL = window.location.origin
console.log('Web app using same origin:', cachedAPIBaseURL)
}
return cachedAPIBaseURL
}
/**
* Check if running in desktop mode (Wails)
*/
export function isDesktopMode() {
return isWailsApp()
}typeof window !== 'undefined' &&
window['go']?.['main']?.['App'] !== undefined
+1 -1
View File
@@ -1,5 +1,5 @@
// Frontend version information // Frontend version information
export const VERSION = '0.2.3' export const VERSION = '0.2.4'
// Git commit will be injected at build time or detected from env // Git commit will be injected at build time or detected from env
export const GIT_COMMIT = import.meta.env.VITE_GIT_COMMIT || 'unknown' export const GIT_COMMIT = import.meta.env.VITE_GIT_COMMIT || 'unknown'
+2 -1
View File
@@ -44,6 +44,7 @@
</style> </style>
<script> <script>
import { getAPIBaseURL } from '../utils/config'
import axios from 'axios' import axios from 'axios'
import { getVersion, getGitCommit } from '../version' import { getVersion, getGitCommit } from '../version'
@@ -80,7 +81,7 @@ export default {
methods: { methods: {
async fetchBackendVersion() { async fetchBackendVersion() {
try { try {
const apiUrl = import.meta.env.VITE_API_URL || 'http://localhost:8180' const apiUrl = await getAPIBaseURL()
const response = await axios.get(`${apiUrl}/api/public/version`) const response = await axios.get(`${apiUrl}/api/public/version`)
if (response.data) { if (response.data) {
+2 -1
View File
@@ -115,6 +115,7 @@
</template> </template>
<script> <script>
import { getAPIBaseURL } from '../utils/config'
import axios from 'axios' import axios from 'axios'
import { getVersion, getGitCommit } from '../version' import { getVersion, getGitCommit } from '../version'
@@ -157,7 +158,7 @@ export default {
methods: { methods: {
async fetchBackendVersion() { async fetchBackendVersion() {
try { try {
const apiUrl = import.meta.env.VITE_API_URL || 'http://localhost:8180' const apiUrl = await getAPIBaseURL()
const response = await axios.get(`${apiUrl}/api/public/systeminfo`) const response = await axios.get(`${apiUrl}/api/public/systeminfo`)
if (response.data) { if (response.data) {
+7 -4
View File
@@ -19,8 +19,11 @@ export default defineConfig({
//port: 8080, //port: 8080,
host: ['bamort.trokan.de','192.168.0.48', 'localhost','terra.local'], host: ['bamort.trokan.de','192.168.0.48', 'localhost','terra.local'],
}, },
//build: { build: {
// minify: false, // When DESKTOP_BUILD=1, output into the Wails embed path so `wails build`
// sourcemap: true, // can embed the frontend directly into the desktop binary.
//}, outDir: process.env.DESKTOP_BUILD ? '../desktop/frontend/dist' : 'dist',
//minify: false,
//sourcemap: true,
},
}) })
+4
View File
@@ -0,0 +1,4 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function GetAPIBaseURL():Promise<string>;
+7
View File
@@ -0,0 +1,7 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function GetAPIBaseURL() {
return window['go']['main']['App']['GetAPIBaseURL']();
}
+24
View File
@@ -0,0 +1,24 @@
{
"name": "@wailsapp/runtime",
"version": "2.0.0",
"description": "Wails Javascript runtime library",
"main": "runtime.js",
"types": "runtime.d.ts",
"scripts": {
},
"repository": {
"type": "git",
"url": "git+https://github.com/wailsapp/wails.git"
},
"keywords": [
"Wails",
"Javascript",
"Go"
],
"author": "Lea Anthony <lea.anthony@gmail.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/wailsapp/wails/issues"
},
"homepage": "https://github.com/wailsapp/wails#readme"
}
+249
View File
@@ -0,0 +1,249 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The electron alternative for Go
(c) Lea Anthony 2019-present
*/
export interface Position {
x: number;
y: number;
}
export interface Size {
w: number;
h: number;
}
export interface Screen {
isCurrent: boolean;
isPrimary: boolean;
width : number
height : number
}
// Environment information such as platform, buildtype, ...
export interface EnvironmentInfo {
buildType: string;
platform: string;
arch: string;
}
// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit)
// emits the given event. Optional data may be passed with the event.
// This will trigger any event listeners.
export function EventsEmit(eventName: string, ...data: any): void;
// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name.
export function EventsOn(eventName: string, callback: (...data: any) => void): () => void;
// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple)
// sets up a listener for the given event name, but will only trigger a given number times.
export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void;
// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce)
// sets up a listener for the given event name, but will only trigger once.
export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void;
// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff)
// unregisters the listener for the given event name.
export function EventsOff(eventName: string, ...additionalEventNames: string[]): void;
// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall)
// unregisters all listeners.
export function EventsOffAll(): void;
// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint)
// logs the given message as a raw message
export function LogPrint(message: string): void;
// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace)
// logs the given message at the `trace` log level.
export function LogTrace(message: string): void;
// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug)
// logs the given message at the `debug` log level.
export function LogDebug(message: string): void;
// [LogError](https://wails.io/docs/reference/runtime/log#logerror)
// logs the given message at the `error` log level.
export function LogError(message: string): void;
// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal)
// logs the given message at the `fatal` log level.
// The application will quit after calling this method.
export function LogFatal(message: string): void;
// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo)
// logs the given message at the `info` log level.
export function LogInfo(message: string): void;
// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning)
// logs the given message at the `warning` log level.
export function LogWarning(message: string): void;
// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload)
// Forces a reload by the main application as well as connected browsers.
export function WindowReload(): void;
// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp)
// Reloads the application frontend.
export function WindowReloadApp(): void;
// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop)
// Sets the window AlwaysOnTop or not on top.
export function WindowSetAlwaysOnTop(b: boolean): void;
// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme)
// *Windows only*
// Sets window theme to system default (dark/light).
export function WindowSetSystemDefaultTheme(): void;
// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme)
// *Windows only*
// Sets window to light theme.
export function WindowSetLightTheme(): void;
// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme)
// *Windows only*
// Sets window to dark theme.
export function WindowSetDarkTheme(): void;
// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter)
// Centers the window on the monitor the window is currently on.
export function WindowCenter(): void;
// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle)
// Sets the text in the window title bar.
export function WindowSetTitle(title: string): void;
// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen)
// Makes the window full screen.
export function WindowFullscreen(): void;
// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen)
// Restores the previous window dimensions and position prior to full screen.
export function WindowUnfullscreen(): void;
// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen)
// Returns the state of the window, i.e. whether the window is in full screen mode or not.
export function WindowIsFullscreen(): Promise<boolean>;
// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize)
// Sets the width and height of the window.
export function WindowSetSize(width: number, height: number): void;
// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize)
// Gets the width and height of the window.
export function WindowGetSize(): Promise<Size>;
// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize)
// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions.
// Setting a size of 0,0 will disable this constraint.
export function WindowSetMaxSize(width: number, height: number): void;
// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize)
// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions.
// Setting a size of 0,0 will disable this constraint.
export function WindowSetMinSize(width: number, height: number): void;
// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition)
// Sets the window position relative to the monitor the window is currently on.
export function WindowSetPosition(x: number, y: number): void;
// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition)
// Gets the window position relative to the monitor the window is currently on.
export function WindowGetPosition(): Promise<Position>;
// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide)
// Hides the window.
export function WindowHide(): void;
// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow)
// Shows the window, if it is currently hidden.
export function WindowShow(): void;
// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise)
// Maximises the window to fill the screen.
export function WindowMaximise(): void;
// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise)
// Toggles between Maximised and UnMaximised.
export function WindowToggleMaximise(): void;
// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise)
// Restores the window to the dimensions and position prior to maximising.
export function WindowUnmaximise(): void;
// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised)
// Returns the state of the window, i.e. whether the window is maximised or not.
export function WindowIsMaximised(): Promise<boolean>;
// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise)
// Minimises the window.
export function WindowMinimise(): void;
// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise)
// Restores the window to the dimensions and position prior to minimising.
export function WindowUnminimise(): void;
// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised)
// Returns the state of the window, i.e. whether the window is minimised or not.
export function WindowIsMinimised(): Promise<boolean>;
// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal)
// Returns the state of the window, i.e. whether the window is normal or not.
export function WindowIsNormal(): Promise<boolean>;
// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour)
// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels.
export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void;
// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall)
// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system.
export function ScreenGetAll(): Promise<Screen[]>;
// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl)
// Opens the given URL in the system browser.
export function BrowserOpenURL(url: string): void;
// [Environment](https://wails.io/docs/reference/runtime/intro#environment)
// Returns information about the environment
export function Environment(): Promise<EnvironmentInfo>;
// [Quit](https://wails.io/docs/reference/runtime/intro#quit)
// Quits the application.
export function Quit(): void;
// [Hide](https://wails.io/docs/reference/runtime/intro#hide)
// Hides the application.
export function Hide(): void;
// [Show](https://wails.io/docs/reference/runtime/intro#show)
// Shows the application.
export function Show(): void;
// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext)
// Returns the current text stored on clipboard
export function ClipboardGetText(): Promise<string>;
// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext)
// Sets a text on the clipboard
export function ClipboardSetText(text: string): Promise<boolean>;
// [OnFileDrop](https://wails.io/docs/reference/runtime/draganddrop#onfiledrop)
// OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
export function OnFileDrop(callback: (x: number, y: number ,paths: string[]) => void, useDropTarget: boolean) :void
// [OnFileDropOff](https://wails.io/docs/reference/runtime/draganddrop#dragandddropoff)
// OnFileDropOff removes the drag and drop listeners and handlers.
export function OnFileDropOff() :void
// Check if the file path resolver is available
export function CanResolveFilePaths(): boolean;
// Resolves file paths for an array of files
export function ResolveFilePaths(files: File[]): void
+242
View File
@@ -0,0 +1,242 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The electron alternative for Go
(c) Lea Anthony 2019-present
*/
export function LogPrint(message) {
window.runtime.LogPrint(message);
}
export function LogTrace(message) {
window.runtime.LogTrace(message);
}
export function LogDebug(message) {
window.runtime.LogDebug(message);
}
export function LogInfo(message) {
window.runtime.LogInfo(message);
}
export function LogWarning(message) {
window.runtime.LogWarning(message);
}
export function LogError(message) {
window.runtime.LogError(message);
}
export function LogFatal(message) {
window.runtime.LogFatal(message);
}
export function EventsOnMultiple(eventName, callback, maxCallbacks) {
return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks);
}
export function EventsOn(eventName, callback) {
return EventsOnMultiple(eventName, callback, -1);
}
export function EventsOff(eventName, ...additionalEventNames) {
return window.runtime.EventsOff(eventName, ...additionalEventNames);
}
export function EventsOffAll() {
return window.runtime.EventsOffAll();
}
export function EventsOnce(eventName, callback) {
return EventsOnMultiple(eventName, callback, 1);
}
export function EventsEmit(eventName) {
let args = [eventName].slice.call(arguments);
return window.runtime.EventsEmit.apply(null, args);
}
export function WindowReload() {
window.runtime.WindowReload();
}
export function WindowReloadApp() {
window.runtime.WindowReloadApp();
}
export function WindowSetAlwaysOnTop(b) {
window.runtime.WindowSetAlwaysOnTop(b);
}
export function WindowSetSystemDefaultTheme() {
window.runtime.WindowSetSystemDefaultTheme();
}
export function WindowSetLightTheme() {
window.runtime.WindowSetLightTheme();
}
export function WindowSetDarkTheme() {
window.runtime.WindowSetDarkTheme();
}
export function WindowCenter() {
window.runtime.WindowCenter();
}
export function WindowSetTitle(title) {
window.runtime.WindowSetTitle(title);
}
export function WindowFullscreen() {
window.runtime.WindowFullscreen();
}
export function WindowUnfullscreen() {
window.runtime.WindowUnfullscreen();
}
export function WindowIsFullscreen() {
return window.runtime.WindowIsFullscreen();
}
export function WindowGetSize() {
return window.runtime.WindowGetSize();
}
export function WindowSetSize(width, height) {
window.runtime.WindowSetSize(width, height);
}
export function WindowSetMaxSize(width, height) {
window.runtime.WindowSetMaxSize(width, height);
}
export function WindowSetMinSize(width, height) {
window.runtime.WindowSetMinSize(width, height);
}
export function WindowSetPosition(x, y) {
window.runtime.WindowSetPosition(x, y);
}
export function WindowGetPosition() {
return window.runtime.WindowGetPosition();
}
export function WindowHide() {
window.runtime.WindowHide();
}
export function WindowShow() {
window.runtime.WindowShow();
}
export function WindowMaximise() {
window.runtime.WindowMaximise();
}
export function WindowToggleMaximise() {
window.runtime.WindowToggleMaximise();
}
export function WindowUnmaximise() {
window.runtime.WindowUnmaximise();
}
export function WindowIsMaximised() {
return window.runtime.WindowIsMaximised();
}
export function WindowMinimise() {
window.runtime.WindowMinimise();
}
export function WindowUnminimise() {
window.runtime.WindowUnminimise();
}
export function WindowSetBackgroundColour(R, G, B, A) {
window.runtime.WindowSetBackgroundColour(R, G, B, A);
}
export function ScreenGetAll() {
return window.runtime.ScreenGetAll();
}
export function WindowIsMinimised() {
return window.runtime.WindowIsMinimised();
}
export function WindowIsNormal() {
return window.runtime.WindowIsNormal();
}
export function BrowserOpenURL(url) {
window.runtime.BrowserOpenURL(url);
}
export function Environment() {
return window.runtime.Environment();
}
export function Quit() {
window.runtime.Quit();
}
export function Hide() {
window.runtime.Hide();
}
export function Show() {
window.runtime.Show();
}
export function ClipboardGetText() {
return window.runtime.ClipboardGetText();
}
export function ClipboardSetText(text) {
return window.runtime.ClipboardSetText(text);
}
/**
* Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
*
* @export
* @callback OnFileDropCallback
* @param {number} x - x coordinate of the drop
* @param {number} y - y coordinate of the drop
* @param {string[]} paths - A list of file paths.
*/
/**
* OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
*
* @export
* @param {OnFileDropCallback} callback - Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
* @param {boolean} [useDropTarget=true] - Only call the callback when the drop finished on an element that has the drop target style. (--wails-drop-target)
*/
export function OnFileDrop(callback, useDropTarget) {
return window.runtime.OnFileDrop(callback, useDropTarget);
}
/**
* OnFileDropOff removes the drag and drop listeners and handlers.
*/
export function OnFileDropOff() {
return window.runtime.OnFileDropOff();
}
export function CanResolveFilePaths() {
return window.runtime.CanResolveFilePaths();
}
export function ResolveFilePaths(files) {
return window.runtime.ResolveFilePaths(files);
}