Compare commits

...

5 Commits

Author SHA1 Message Date
Frank 09d12d3d8c extra learn_categories added to spell maintenance 2026-02-27 11:59:16 +01:00
Bardioc26 6ac04b2ae1 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:16 +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
48 changed files with 1993 additions and 90 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
const Version = "0.2.4"
const Version = "0.2.5"
var (
// 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
}
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) {
id := c.Param("id")
+1
View File
@@ -47,6 +47,7 @@ func RegisterRoutes(r *gin.RouterGroup) {
maintGrp.PUT("/spells/:id", UpdateMDSpell)
maintGrp.PUT("/spells-enhanced/:id", UpdateEnhancedMDSpell) // New enhanced endpoint
maintGrp.POST("/spells", AddSpell)
maintGrp.POST("/spells-enhanced", CreateEnhancedMDSpell) // New enhanced endpoint
maintGrp.DELETE("/spells/:id", DeleteMDSpell)
maintGrp.PUT("/equipment/:id", UpdateMDEquipment)
+43 -3
View File
@@ -66,6 +66,15 @@ func UpdateSpellWithCategories(spellID uint, req SpellUpdateRequest) error {
})
}
// CreateSpellWithCategories creates a new spell
func CreateSpellWithCategories(req SpellUpdateRequest) (*models.Spell, error) {
spell := req.Spell
if err := database.DB.Create(&spell).Error; err != nil {
return nil, err
}
return &spell, nil
}
// ===== Handler Functions =====
// GetEnhancedMDSpells returns spells with enhanced information
@@ -87,11 +96,18 @@ func GetEnhancedMDSpells(c *gin.Context) {
respondWithError(c, http.StatusInternalServerError, "Failed to retrieve spell categories: "+err.Error())
return
}
// Get spell Learn_categories
learnCategories, err := spell.GetSpellLearnCategories()
if err != nil {
respondWithError(c, http.StatusInternalServerError, "Failed to retrieve spell learn categories: "+err.Error())
return
}
c.JSON(http.StatusOK, gin.H{
"spells": spells,
"sources": sources,
"categories": categories,
"spells": spells,
"sources": sources,
"categories": categories,
"learnCategories": learnCategories,
})
}
@@ -145,3 +161,27 @@ func UpdateEnhancedMDSpell(c *gin.Context) {
c.JSON(http.StatusOK, spell)
}
// CreateEnhancedMDSpell creates a new spell
func CreateEnhancedMDSpell(c *gin.Context) {
var req SpellUpdateRequest
if err := c.ShouldBindJSON(&req); err != nil {
respondWithError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
return
}
spell, err := CreateSpellWithCategories(req)
if err != nil {
respondWithError(c, http.StatusInternalServerError, "Failed to create spell: "+err.Error())
return
}
// Return created spell with enhanced information
spellWithCats, err := GetSpellWithCategories(spell.ID)
if err != nil {
respondWithError(c, http.StatusInternalServerError, "Failed to retrieve created spell")
return
}
c.JSON(http.StatusCreated, spellWithCats)
}
@@ -0,0 +1,153 @@
package gsmaster
import (
"bamort/database"
"bamort/models"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestUpdateSpellWithLearningCategory(t *testing.T) {
// Set test environment
setupTestEnvironment(t)
database.SetupTestDB(true)
defer database.ResetTestDB()
// Create a test spell
spell := models.Spell{
Name: "Testzauber",
Category: "Wunder",
LearningCategory: "",
Stufe: 1,
GameSystem: "midgard",
}
err := database.DB.Create(&spell).Error
require.NoError(t, err, "Failed to create test spell")
// Test 1: Update learning_category
spell.LearningCategory = "Wundertat"
updateReq := SpellUpdateRequest{Spell: spell}
err = UpdateSpellWithCategories(spell.ID, updateReq)
assert.NoError(t, err, "Failed to update spell with learning_category")
// Verify update
var updated models.Spell
err = database.DB.First(&updated, spell.ID).Error
require.NoError(t, err)
assert.Equal(t, "Wundertat", updated.LearningCategory, "Learning category not updated correctly")
assert.Equal(t, "Wunder", updated.Category, "Category should remain unchanged")
// Test 2: Update both categories
updated.Category = "Verändern"
updated.LearningCategory = "Zauberlied"
updateReq2 := SpellUpdateRequest{Spell: updated}
err = UpdateSpellWithCategories(updated.ID, updateReq2)
assert.NoError(t, err, "Failed to update both categories")
// Verify both were updated
var final models.Spell
err = database.DB.First(&final, updated.ID).Error
require.NoError(t, err)
assert.Equal(t, "Verändern", final.Category, "Category not updated")
assert.Equal(t, "Zauberlied", final.LearningCategory, "Learning category not updated")
}
func TestCreateSpellWithLearningCategory(t *testing.T) {
setupTestEnvironment(t)
database.SetupTestDB(true)
defer database.ResetTestDB()
// Test: Create spell with learning_category
spell := models.Spell{
Name: "Neuer Zauber",
Category: "Beherrschen",
LearningCategory: "Runenstab",
Stufe: 2,
GameSystem: "midgard",
AP: "3",
}
err := database.DB.Create(&spell).Error
require.NoError(t, err, "Failed to create spell with learning_category")
// Verify creation
var created models.Spell
err = database.DB.Where("name = ?", "Neuer Zauber").First(&created).Error
require.NoError(t, err)
assert.Equal(t, "Beherrschen", created.Category)
assert.Equal(t, "Runenstab", created.LearningCategory)
assert.Equal(t, 2, created.Stufe)
}
func TestLearningCategoryDefaultEmpty(t *testing.T) {
setupTestEnvironment(t)
database.SetupTestDB(true)
defer database.ResetTestDB()
// Test: Create spell without learning_category
spell := models.Spell{
Name: "Zauber ohne LearningCategory",
Category: "Normal",
Stufe: 1,
GameSystem: "midgard",
}
err := database.DB.Create(&spell).Error
require.NoError(t, err)
// Verify learning_category is empty (not nil)
var created models.Spell
err = database.DB.First(&created, spell.ID).Error
require.NoError(t, err)
assert.Equal(t, "", created.LearningCategory, "Learning category should be empty string by default")
assert.Equal(t, "Normal", created.Category)
}
func TestCreateEnhancedMDSpellEndpoint(t *testing.T) {
setupTestEnvironment(t)
database.SetupTestDB(true)
defer database.ResetTestDB()
// Test: Create spell with learning_category via endpoint
spell := models.Spell{
Name: "Test Endpoint Zauber",
Category: "Erkennen",
LearningCategory: "Erkenntnismagie",
Stufe: 3,
GameSystem: "midgard",
AP: "2",
}
req := SpellUpdateRequest{Spell: spell}
created, err := CreateSpellWithCategories(req)
require.NoError(t, err, "Failed to create spell")
assert.NotZero(t, created.ID, "Created spell should have an ID")
assert.Equal(t, "Erkennen", created.Category)
assert.Equal(t, "Erkenntnismagie", created.LearningCategory)
assert.Equal(t, 3, created.Stufe)
}
func TestGetSpellLearningCategories(t *testing.T) {
setupTestEnvironment(t)
database.SetupTestDB(true)
defer database.ResetTestDB()
var spell models.Spell
learningCategories, err := spell.GetSpellLearnCategories()
require.NoError(t, err, "Failed to get spell learning categories")
assert.Contains(t, learningCategories, "Spruch", "Learning categories should include 'Spruch'")
assert.Contains(t, learningCategories, "Runenstab", "Learning categories should include 'Runenstab'")
assert.Contains(t, learningCategories, "Zauberlied", "Learning categories should include 'Zauberlied'")
assert.Contains(t, learningCategories, "Wundertat", "Learning categories should include 'Wundertat'")
assert.Contains(t, learningCategories, "Dweomer", "Learning categories should include 'Dweomer'")
assert.Contains(t, learningCategories, "Thaumatherapie", "Learning categories should include 'Thaumatherapie'")
assert.Contains(t, learningCategories, "Zaubersalz", "Learning categories should include 'Zaubersalz'")
assert.Contains(t, learningCategories, "Rune", "Learning categories should include 'Rune'")
assert.Contains(t, learningCategories, "Siegel", "Learning categories should include 'Siegel'")
}
+16
View File
@@ -438,6 +438,22 @@ func (object *Spell) GetSpellCategories() ([]string, error) {
return categories, nil
}
func (object *Spell) GetSpellLearnCategories() ([]string, error) {
var categories []string
gs := GetGameSystem(object.GameSystemId, object.GameSystem)
result := database.DB.Model(&Spell{}).
Where("game_system = ? OR game_system_id = ?", gs.Name, gs.ID).
Distinct().
Pluck("learning_category", &categories)
if result.Error != nil {
return nil, result.Error
}
return categories, nil
}
func (object *Spell) ensureGameSystem() {
gs := GetGameSystem(object.GameSystemId, object.GameSystem)
object.GameSystemId = gs.ID
+31 -17
View File
@@ -8,23 +8,37 @@ import (
)
func SetupGin(r *gin.Engine) {
// Build allowed origins list from configuration
allowedOrigins := []string{
config.Cfg.FrontendURL,
"http://localhost:5173", // Development frontend
"http://192.168.0.48:5173", // Development frontend
"http://192.168.0.36:5173", // Development frontend
"https://bamort.trokan.de", // Production frontend
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{
config.Cfg.FrontendURL,
"http://localhost:5173", // Development frontend
"http://192.168.0.48:5173", // Development frontend
"http://192.168.0.36:5173", // Development frontend
"https://bamort.trokan.de", // Production frontend
"http://wails.localhost", // Wails desktop WebView
}
corsConfig = cors.Config{
AllowOrigins: allowedOrigins,
AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
MaxAge: 12 * 3600,
}
}
// Add CORS middleware
r.Use(cors.New(cors.Config{
//AllowOrigins: []string{"http://localhost:3000"}, // Replace with your frontend's URL
AllowOrigins: allowedOrigins,
AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"},
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
MaxAge: 12 * 3600, // Cache preflight for 12 hours
}))
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
# API Configuration
# API_URL=http://localhost:8180
# Database Configuration (for development)
DATABASE_TYPE=mysql
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_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_PORT=8180
# Frontend Configuration
BASE_URL=http://localhost:5173
TEMPLATES_DIR=./templates
EXPORT_TEMP_DIR=./export_temp
+16 -3
View File
@@ -1,9 +1,16 @@
# 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
VITE_API_URL=https://backend.domain.de
API_PORT=8180
#- Database Configuration Backend
DATABASE_TYPE=mysql
@@ -20,3 +27,9 @@ BASE_URL=https://frontend.domain.de
TEMPLATES_DIR=./templates
EXPORT_TEMP_DIR=./export_temp
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
# 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
VITE_API_URL=https://bamort-api.trokan.de
# Database Configuration Backend
API_PORT=8180
DATABASE_TYPE=mysql
#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_USER=bamort
API_PORT=8180
# Frontend Configuration
BASE_URL=https://bamort.trokan.de
TEMPLATES_DIR=./templates
EXPORT_TEMP_DIR=./export_temp
+12 -17
View File
@@ -1,42 +1,37 @@
# =========== 1) Build stage ===========
FROM node:22-alpine AS build
# Accept build arguments for Vite environment variables
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
# No build args needed - using runtime configuration instead
WORKDIR /usr/src/app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm install
# Copy the rest of the frontend code
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
# =========== 2) Serve stage ===========
FROM nginx:alpine
# 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 production build to Nginx html folder
COPY --from=build /usr/src/app/dist /usr/share/nginx/html
# Copy custom nginx configuration for SPA routing
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 80
# Run Nginx in foreground
CMD ["nginx", "-g", "daemon off;"]
# Use custom entrypoint that generates config.json from environment
ENTRYPOINT ["/docker-entrypoint.sh"]
+6 -2
View File
@@ -9,8 +9,12 @@ COPY package*.json ./
# Install dependencies
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 5173
# Start development server with host binding for Docker
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
# Use entrypoint that generates config.json before starting Vite
ENTRYPOINT ["/docker-entrypoint.sh"]
+1 -7
View File
@@ -25,18 +25,12 @@ services:
build:
context: ../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
ports:
- "8181:80"
environment:
- NODE_ENV=production
- VITE_API_URL=${API_URL:-https://bamort.trokan.de:8180}
- VITE_BASE_URL=${BASE_URL:-https://bamort.trokan.de}
- VITE_API_PORT=${API_PORT:-8180}
- API_URL=${API_URL:-https://bamort-api.trokan.de}
depends_on:
- backend
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
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
export GIT_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
echo "📝 Git Commit: $GIT_COMMIT"
echo "📦 Building and starting development containers..."
echo "🔧 Frontend will use API: ${API_URL:-http://localhost:8180}"
# Stoppe vorhandene Container
docker-compose -f docker-compose.dev.yml down
# 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 "📱 Frontend: http://localhost:5173"
echo "🔌 Backend: http://localhost:8180"
echo "🗄️ phpMyAdmin: http://localhost:8081"
+24 -3
View File
@@ -11,14 +11,35 @@ fi
# Gehe ins Docker-Verzeichnis
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 "🔧 Frontend will use API: ${API_URL:-https://bamort-api.trokan.de}"
# 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
docker-compose -f docker-compose.yml down
# 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
*.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
.vscode/*
!.vscode/extensions.json
+1 -1
View File
@@ -1,6 +1,6 @@
# Frontend Version Management
## Current Version: 0.2.3
## Current Version: 0.2.4
The frontend version is managed independently from the backend.
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "bamort-frontend",
"version": "0.1.29",
"version": "0.2.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "bamort-frontend",
"version": "0.1.29",
"version": "0.2.4",
"license": "SEE LICENSE IN LICENSE",
"dependencies": {
"axios": "^1.7.9",
+2 -1
View File
@@ -1,12 +1,13 @@
{
"name": "bamort-frontend",
"version": "0.2.3",
"version": "0.2.4",
"private": true,
"license": "SEE LICENSE IN LICENSE",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"build:desktop": "DESKTOP_BUILD=1 vite build",
"preview": "vite preview"
},
"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 -->
<!-- <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> -->
<!-- Submenu -->
@@ -156,6 +156,12 @@ export default {
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>
+75 -5
View File
@@ -55,6 +55,15 @@
</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">
<thead>
<tr>
@@ -384,6 +393,29 @@
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>
<script>
@@ -431,6 +463,7 @@ export default {
editingSkillId: null,
editingField: null,
editValue: '',
showNameEditWarning: false,
isLoading: false
};
@@ -752,6 +785,11 @@ export default {
this.editingField = field;
this.editValue = skill[field] || '';
// Show warning when editing skill name
if (field === 'name') {
this.showNameEditWarning = true;
}
this.$nextTick(() => {
const input = this.$refs.editInput;
if (input) {
@@ -779,19 +817,50 @@ export default {
}
}
// Update local character object
skill[field] = newValue;
// Find and update the original skill in character.fertigkeiten or character.waffenfertigkeiten
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 {
// 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();
} catch (error) {
console.error('Failed to update skill:', error);
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.editingField = null;
this.editValue = '';
this.showNameEditWarning = false;
},
isEditingSkill(skill, field) {
@@ -129,6 +129,7 @@
{{ $t('spell.category') }}
<button @click="sortBy('category')">{{ sortField === 'category' ? (sortAsc ? '' : '') : '-' }}</button>
</th>
<th class="cd-table-header">{{ $t('spell.learning_category') || 'Learning Category' }}</th>
<th class="cd-table-header">
{{ $t('spell.name') }}
<button @click="sortBy('name')">{{ sortField === 'name' ? (sortAsc ? '' : '') : '-' }}</button>
@@ -150,7 +151,7 @@
<tbody>
<tr v-if="creatingNew">
<td>New</td>
<td colspan="14">
<td colspan="15">
<div class="edit-form">
<div class="edit-row">
<div class="edit-field">
@@ -165,6 +166,15 @@
</option>
</select>
</div>
<div class="edit-field">
<label>{{ $t('spell.learning_category') || 'Learning Category' }}:</label>
<select v-model="newItem.learning_category" style="width:150px;">
<option value="">-</option>
<option v-for="category in availableLearnCategories" :key="category" :value="category">
{{ category }}
</option>
</select>
</div>
<div class="edit-field">
<label>{{ $t('spell.level') }}:</label>
<input v-model.number="newItem.level" type="number" style="width:60px;" />
@@ -248,6 +258,7 @@
<tr v-if="editingIndex !== index">
<td>{{ dtaItem.id || '' }}</td>
<td>{{ dtaItem.category|| '-' }}</td>
<td>{{ dtaItem.learning_category || '-' }}</td>
<td>{{ dtaItem.name || '-' }}</td>
<td>{{ dtaItem.level || '0' }}</td>
<td>{{ dtaItem.ap || '0' }}</td>
@@ -267,7 +278,7 @@
<!-- Edit Mode -->
<tr v-else>
<td><input v-model="editedItem.id" style="width:20px;" disabled /></td>
<td colspan="14">
<td colspan="15">
<!-- Expanded edit form -->
<div class="edit-form">
<div class="edit-row">
@@ -283,6 +294,15 @@
</option>
</select>
</div>
<div class="edit-field">
<label>{{ $t('spell.learning_category') || 'Learning Category' }}:</label>
<select v-model="editedItem.learning_category" style="width:150px;">
<option value="">-</option>
<option v-for="category in availableLearnCategories" :key="category" :value="category">
{{ category }}
</option>
</select>
</div>
<div class="edit-field">
<label>{{ $t('spell.level') }}:</label>
<input v-model.number="editedItem.level" type="number" style="width:60px;" />
@@ -519,6 +539,7 @@ export default {
filterQuelle: '',
enhancedSpells: [],
availableSources: [],
availableLearnCategories: [],
gameSystems: [],
selectedSystemId: null,
creatingNew: false,
@@ -663,6 +684,7 @@ export default {
const response = await API.get('/api/maintenance/spells-enhanced')
this.enhancedSpells = response.data.spells || []
this.availableSources = response.data.sources || []
this.availableLearnCategories = response.data.learnCategories || []
// Also update mdata for compatibility
if (response.data.categories) {
this.mdata.spellcategories = response.data.categories
@@ -725,6 +747,7 @@ export default {
this.newItem = {
name: '',
category: this.mdata.spellcategories?.[0] || '',
learning_category: '',
level: 0,
ap: '',
zauberdauer: '',
+3 -1
View File
@@ -177,6 +177,7 @@ export default {
spell:{
id:'ID',
category:'Kategorie',
learning_category:'Lernkategorie',
name:'Name',
description:'Beschreibung',
level:'Stufe',
@@ -563,7 +564,8 @@ export default {
bRollTooltip: 'B würfeln: 1d6 + Modifikator je nach Rasse'
},
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: {
+3 -1
View File
@@ -173,6 +173,7 @@ export default {
spell:{
id:'ID',
category:'Category',
learning_category:'Learning Category',
name:'Name',
description:'Description',
level:'Level',
@@ -559,7 +560,8 @@ export default {
bRollTooltip: 'Roll B: 1d6 + modifier based on race'
},
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: {
+26 -5
View File
@@ -1,12 +1,33 @@
import axios from 'axios'
import { getAPIBaseURL } from './config'
const API = axios.create({
baseURL: import.meta.env.VITE_API_URL || 'https://bamort-api.trokan.de', // Use env variable with fallback
})
// Create API instance without baseURL - will be set dynamically
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(
(config) => {
async (config) => {
// Ensure baseURL is set before request
await ensureBaseURL()
const token = localStorage.getItem('token')
if (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
export const VERSION = '0.2.3'
export const VERSION = '0.2.4'
// Git commit will be injected at build time or detected from env
export const GIT_COMMIT = import.meta.env.VITE_GIT_COMMIT || 'unknown'
+2 -1
View File
@@ -44,6 +44,7 @@
</style>
<script>
import { getAPIBaseURL } from '../utils/config'
import axios from 'axios'
import { getVersion, getGitCommit } from '../version'
@@ -80,7 +81,7 @@ export default {
methods: {
async fetchBackendVersion() {
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`)
if (response.data) {
+2 -1
View File
@@ -115,6 +115,7 @@
</template>
<script>
import { getAPIBaseURL } from '../utils/config'
import axios from 'axios'
import { getVersion, getGitCommit } from '../version'
@@ -157,7 +158,7 @@ export default {
methods: {
async fetchBackendVersion() {
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`)
if (response.data) {
+7 -4
View File
@@ -19,8 +19,11 @@ export default defineConfig({
//port: 8080,
host: ['bamort.trokan.de','192.168.0.48', 'localhost','terra.local'],
},
//build: {
// minify: false,
// sourcemap: true,
//},
build: {
// When DESKTOP_BUILD=1, output into the Wails embed path so `wails build`
// 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);
}