wails/v3/internal/defaults/defaults.go
Lea Anthony 431869bf84 feat(setup): add global defaults, light/dark mode, and UI improvements
- Add global defaults config stored in ~/.config/wails/defaults.yaml
- Add light/dark mode toggle with theme persistence
- Add PKGBUILD support to Linux build formats display
- Add macOS signing clarification (public identifiers vs Keychain storage)
- Fix spinner animation using CSS animate-spin
- Add signing defaults for macOS and Windows code signing
- Compact defaults page layout with 2-column design
- Add Wails logo with proper light/dark theme variants

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-07 17:40:53 +11:00

219 lines
5.7 KiB
Go

// Package defaults provides functionality for loading and saving global default settings
// for Wails projects. Settings are stored in ~/.config/wails/defaults.yaml
package defaults
import (
"os"
"path/filepath"
"time"
"gopkg.in/yaml.v3"
)
// GlobalDefaults represents the user's default project settings
// These are stored in ~/.config/wails/defaults.yaml and used when creating new projects
type GlobalDefaults struct {
// Author information
Author AuthorDefaults `json:"author" yaml:"author"`
// Default project settings
Project ProjectDefaults `json:"project" yaml:"project"`
}
// AuthorDefaults contains the author's information
type AuthorDefaults struct {
Name string `json:"name" yaml:"name"`
Company string `json:"company" yaml:"company"`
}
// ProjectDefaults contains default project settings
type ProjectDefaults struct {
// ProductIdentifierPrefix is the prefix for app identifiers (e.g., "com.mycompany")
ProductIdentifierPrefix string `json:"productIdentifierPrefix" yaml:"productIdentifierPrefix"`
// DefaultTemplate is the default frontend template to use
DefaultTemplate string `json:"defaultTemplate" yaml:"defaultTemplate"`
// Copyright template - can include {year} and {company} placeholders
CopyrightTemplate string `json:"copyrightTemplate" yaml:"copyrightTemplate"`
// Description template for new projects - can include {name} placeholder
DescriptionTemplate string `json:"descriptionTemplate" yaml:"descriptionTemplate"`
// Default product version for new projects
DefaultVersion string `json:"defaultVersion" yaml:"defaultVersion"`
}
// Default returns sensible defaults for first-time users
func Default() GlobalDefaults {
return GlobalDefaults{
Author: AuthorDefaults{
Name: "",
Company: "",
},
Project: ProjectDefaults{
ProductIdentifierPrefix: "com.example",
DefaultTemplate: "vanilla",
CopyrightTemplate: "© {year}, {company}",
DescriptionTemplate: "A {name} application",
DefaultVersion: "0.1.0",
},
}
}
// GetConfigDir returns the path to the Wails config directory
func GetConfigDir() (string, error) {
// Use XDG_CONFIG_HOME if set, otherwise use ~/.config
configHome := os.Getenv("XDG_CONFIG_HOME")
if configHome == "" {
homeDir, err := os.UserHomeDir()
if err != nil {
return "", err
}
configHome = filepath.Join(homeDir, ".config")
}
return filepath.Join(configHome, "wails"), nil
}
// GetDefaultsPath returns the path to the defaults.yaml file
func GetDefaultsPath() (string, error) {
configDir, err := GetConfigDir()
if err != nil {
return "", err
}
return filepath.Join(configDir, "defaults.yaml"), nil
}
// Load loads the global defaults from the config file
// Returns default values if the file doesn't exist
func Load() (GlobalDefaults, error) {
defaults := Default()
path, err := GetDefaultsPath()
if err != nil {
return defaults, err
}
data, err := os.ReadFile(path)
if err != nil {
if os.IsNotExist(err) {
return defaults, nil
}
return defaults, err
}
if err := yaml.Unmarshal(data, &defaults); err != nil {
return Default(), err
}
return defaults, nil
}
// Save saves the global defaults to the config file
func Save(defaults GlobalDefaults) error {
path, err := GetDefaultsPath()
if err != nil {
return err
}
// Ensure the config directory exists
configDir := filepath.Dir(path)
if err := os.MkdirAll(configDir, 0755); err != nil {
return err
}
data, err := yaml.Marshal(&defaults)
if err != nil {
return err
}
return os.WriteFile(path, data, 0644)
}
// GenerateCopyright generates a copyright string from the template
func (d *GlobalDefaults) GenerateCopyright() string {
template := d.Project.CopyrightTemplate
if template == "" {
template = "© {year}, {company}"
}
year := time.Now().Format("2006")
company := d.Author.Company
if company == "" {
company = "My Company"
}
result := template
result = replaceAll(result, "{year}", year)
result = replaceAll(result, "{company}", company)
return result
}
// GenerateProductIdentifier generates a product identifier from prefix and project name
func (d *GlobalDefaults) GenerateProductIdentifier(projectName string) string {
prefix := d.Project.ProductIdentifierPrefix
if prefix == "" {
prefix = "com.example"
}
return prefix + "." + sanitizeIdentifier(projectName)
}
// GenerateDescription generates a description string from the template
func (d *GlobalDefaults) GenerateDescription(projectName string) string {
template := d.Project.DescriptionTemplate
if template == "" {
template = "A {name} application"
}
return replaceAll(template, "{name}", projectName)
}
// GetDefaultVersion returns the default version or the fallback
func (d *GlobalDefaults) GetDefaultVersion() string {
if d.Project.DefaultVersion != "" {
return d.Project.DefaultVersion
}
return "0.1.0"
}
// replaceAll replaces all occurrences of old with new in s
func replaceAll(s, old, new string) string {
result := s
for {
newResult := replaceOnce(result, old, new)
if newResult == result {
break
}
result = newResult
}
return result
}
func replaceOnce(s, old, new string) string {
for i := 0; i <= len(s)-len(old); i++ {
if s[i:i+len(old)] == old {
return s[:i] + new + s[i+len(old):]
}
}
return s
}
// sanitizeIdentifier creates a valid identifier from a project name
func sanitizeIdentifier(name string) string {
var result []byte
for i := 0; i < len(name); i++ {
c := name[i]
if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') {
result = append(result, c)
}
}
if len(result) == 0 {
return "app"
}
// Lowercase the result
for i := range result {
if result[i] >= 'A' && result[i] <= 'Z' {
result[i] = result[i] + 32
}
}
return string(result)
}