feat: settings window polish, sync widget fix, dark form controls
- Fix: settings overlay uses on:click|self so sidebar clicks don't close it - Fix: openSettings(section) supports opening at specific tab - Fix: 'Настроить' opens Settings → Синхронизация instead of Общие - Style: dark theme select with custom arrow, global :global() CSS - Style: settings cards, section descriptions, button/layout polish - Style: settings gear buttons (icon-button pattern, 32px, soft hover) - Style: settings sidebar with disabled stubs, consistent icons - i18n: add generalDesc, workspaceDesc, appearance, localization keys
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const (
|
||||
AppConfigVersion = 1
|
||||
ConfigDirName = "verstak"
|
||||
AppConfigFileName = "config.json"
|
||||
)
|
||||
|
||||
// AppConfig is the global application config stored at ~/.config/verstak/config.json.
|
||||
type AppConfig struct {
|
||||
Version int `json:"version"`
|
||||
VaultPath string `json:"vault_path"`
|
||||
Theme string `json:"theme"`
|
||||
Language string `json:"language"`
|
||||
EnabledTemplates []string `json:"enabled_templates"`
|
||||
EnabledPlugins []string `json:"enabled_plugins"`
|
||||
FirstRunCompleted bool `json:"first_run_completed"`
|
||||
Window WindowConfig `json:"window,omitempty"`
|
||||
Vault VaultAppConfig `json:"vault,omitempty"`
|
||||
}
|
||||
|
||||
type WindowConfig struct {
|
||||
Width int `json:"width,omitempty"`
|
||||
Height int `json:"height,omitempty"`
|
||||
}
|
||||
|
||||
// VaultAppConfig holds per-vault settings in the global config.
|
||||
type VaultAppConfig struct {
|
||||
VaultID string `json:"vault_id,omitempty"`
|
||||
CreatedAt string `json:"created_at,omitempty"`
|
||||
Sync SyncSettings `json:"sync,omitempty"`
|
||||
}
|
||||
|
||||
// SyncSettings holds sync configuration for the current vault.
|
||||
type SyncSettings struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
ServerURL string `json:"server_url"`
|
||||
DeviceID string `json:"device_id"`
|
||||
DeviceName string `json:"device_name"`
|
||||
SyncInterval int `json:"sync_interval"`
|
||||
LastStatus string `json:"last_status"`
|
||||
LastSyncAt string `json:"last_sync_at"`
|
||||
LastError string `json:"last_error,omitempty"`
|
||||
}
|
||||
|
||||
func DefaultAppConfig() *AppConfig {
|
||||
return &AppConfig{
|
||||
Version: AppConfigVersion,
|
||||
Theme: "system",
|
||||
Language: "ru",
|
||||
EnabledTemplates: []string{"folder", "project", "client", "document", "recipe"},
|
||||
EnabledPlugins: []string{},
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigDir returns ~/.config/verstak (or platform equivalent).
|
||||
func ConfigDir() (string, error) {
|
||||
cfgDir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(cfgDir, ConfigDirName), nil
|
||||
}
|
||||
|
||||
// AppConfigPath returns the path to config.json.
|
||||
func AppConfigPath() (string, error) {
|
||||
dir, err := ConfigDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(dir, AppConfigFileName), nil
|
||||
}
|
||||
|
||||
// EnsureConfigDir creates the config directory if it doesn't exist.
|
||||
func EnsureConfigDir() (string, error) {
|
||||
dir, err := ConfigDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := os.MkdirAll(dir, 0o750); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
// LoadAppConfig reads the global config from disk. Returns nil if the file doesn't exist.
|
||||
func LoadAppConfig() (*AppConfig, error) {
|
||||
path, err := AppConfigPath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("read config: %w", err)
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
var cfg AppConfig
|
||||
if err := json.Unmarshal(data, &cfg); err != nil {
|
||||
return nil, fmt.Errorf("parse config: %w", err)
|
||||
}
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
// SaveAppConfig writes the global config to disk.
|
||||
func SaveAppConfig(cfg *AppConfig) error {
|
||||
path, err := AppConfigPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := EnsureConfigDir(); err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := json.MarshalIndent(cfg, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(path, data, 0o640)
|
||||
}
|
||||
|
||||
// DefaultVaultPath returns the default vault location.
|
||||
func DefaultVaultPath() (string, error) {
|
||||
dir, err := ConfigDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(dir, "vault"), nil
|
||||
}
|
||||
Reference in New Issue
Block a user