Skip to content

Commit 0c5b504

Browse files
committed
new: default config setup
1 parent 9508163 commit 0c5b504

File tree

8 files changed

+281
-51
lines changed

8 files changed

+281
-51
lines changed

TODO.md

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
Rewrite Ghost Chat from Electron to **Wails v2** with a **Go** backend and **React + TypeScript** frontend.
66

7+
### Learning approach
8+
When introducing new Go syntax or concepts, always provide a **Node.js/TypeScript equivalent** for comparison. For example: `goroutine``Promise`/`async`, `channel``EventEmitter`, `interface``interface` (but implicit), `defer``finally`, etc.
9+
710
### Key changes from the original app
811
- **Drop**: Kick support, JChat/KapChat third-party renderers, auto-updater (for now), custom CSS/JS injection
912
- **Keep**: Twitch, YouTube, External sources, system tray, i18n, global hotkeys, transparent overlay, dark/light theme
@@ -28,14 +31,14 @@ Rewrite Ghost Chat from Electron to **Wails v2** with a **Go** backend and **Rea
2831
- [x] Install Go (1.22+) via your package manager or https://go.dev/dl
2932
- [x] Verify: `go version` → go1.25.7 linux/amd64
3033
- [x] Set up your editor (VS Code + Go extension, or GoLand)
31-
- [ ] **Learn**: Read [A Tour of Go](https://go.dev/tour/) — covers variables, types, functions, control flow, structs, interfaces, goroutines, channels
34+
- [x] **Learn**: Read [A Tour of Go](https://go.dev/tour/) — covers variables, types, functions, control flow, structs, interfaces, goroutines, channels
3235

3336
### 0.2 Go warm-up exercises
3437
- [x] Write a "hello world" main package
3538
- [x] Write a program that reads/writes a JSON file (this is the pattern you'll use for the config store)
3639
- [x] Write a program that connects to a WebSocket and prints incoming messages (this is the pattern for Twitch IRC)
3740
- [x] Write a program that makes HTTP requests and parses JSON responses (this is the pattern for YouTube API)
38-
- [ ] **Learn**: Understand `struct`, `interface`, `error` handling, `goroutine` + `channel`, `context.Context` for cancellation
41+
- [x] **Learn**: Understand `struct`, `interface`, `error` handling, `goroutine` + `channel`, `context.Context` for cancellation
3942

4043
### 0.3 Install Wails and scaffold project
4144
- [x] Install Wails CLI: `go install github.com/wailsapp/wails/v2/cmd/wails@latest`
@@ -47,7 +50,7 @@ Rewrite Ghost Chat from Electron to **Wails v2** with a **Go** backend and **Rea
4750
- `frontend/` — React + Vite app
4851
- `wails.json` — project config
4952
- [x] Run `wails dev` and confirm the template app launches
50-
- [ ] **Learn**: Read [Wails v2 docs](https://wails.io/docs/introduction) — understand bindings, events, runtime API, window options
53+
- [x] **Learn**: Read [Wails v2 docs](https://wails.io/docs/introduction) — understand bindings, events, runtime API, window options
5154

5255
### 0.4 Understand Wails ↔ Frontend communication
5356
- [x] Add a Go method to `app.go` that returns a string, call it from frontend
@@ -62,7 +65,7 @@ Rewrite Ghost Chat from Electron to **Wails v2** with a **Go** backend and **Rea
6265
> **Goal**: Set up the real project structure, config persistence, and window basics.
6366
6467
### 1.1 Project structure
65-
- [ ] Reorganize the scaffolded project into this layout:
68+
- [x] Reorganize the scaffolded project into this layout:
6669
```
6770
ghost-chat/
6871
├── main.go # Entry point
@@ -101,28 +104,16 @@ Rewrite Ghost Chat from Electron to **Wails v2** with a **Go** backend and **Rea
101104
│ └── package.json
102105
└── wails.json
103106
```
104-
- [ ] **Learn**: The `internal/` directory is a Go convention — packages inside cannot be imported by external modules. It signals "private to this project."
107+
- [x] **Learn**: The `internal/` directory is a Go convention — packages inside cannot be imported by external modules. It signals "private to this project."
105108

106109
### 1.2 Config store (Go)
107-
- [ ] Define the `Config` struct in `internal/config/config.go` mirroring your app's settings:
108-
```go
109-
type Config struct {
110-
Version string `json:"version"`
111-
Window WindowState `json:"window"`
112-
Settings Settings `json:"settings"`
113-
General General `json:"general"`
114-
Keybinds Keybinds `json:"keybinds"`
115-
Twitch TwitchConfig `json:"twitch"`
116-
YouTube YouTubeConfig `json:"youtube"`
117-
External ExternalConfig `json:"external"`
118-
}
119-
```
120-
- [ ] Implement `Load(path string) (*Config, error)` — reads JSON file, returns config with defaults for missing fields
121-
- [ ] Implement `Save(path string) error` — writes config to JSON file
122-
- [ ] Implement `GetConfigPath() string` — platform-specific config directory (`os.UserConfigDir()`)
123-
- [ ] Add default values (equivalent to `StoreDefaults` in the old app)
124-
- [ ] Write unit tests for Load/Save round-trip
125-
- [ ] **Learn**: Go error handling (`if err != nil`), `encoding/json` struct tags, `os` package for file I/O
110+
- [x] Define the `Config` struct in `internal/config/config.go` mirroring your app's settings
111+
- [x] Implement `Load(path string) (*Config, error)` — reads JSON file, returns config with defaults for missing fields
112+
- [x] Implement `Save(config *Config, path string) error` — writes config to JSON file
113+
- [x] Implement `GetConfigPath() (string, error)` — platform-specific config directory (`os.UserConfigDir()`)
114+
- [x] Add default values (equivalent to `StoreDefaults` in the old app)
115+
- [x] Write unit tests for Load/Save round-trip
116+
- [x] **Learn**: Go error handling (`if err != nil`), `encoding/json` struct tags, `os` package for file I/O
126117

127118
### 1.3 Config migration system (Go)
128119
- [ ] Implement a migration runner that compares `config.Version` against app version

frontend/package.json.md5

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
b9e3d4fa19dd7dd784cd2992a8921790
1+
cc14ab660329973de150ee1544b37acf

frontend/wailsjs/go/main/App.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
// This file is automatically generated. DO NOT EDIT
44

55
export function GetAppInfo() {
6-
return window['go']['main']['App']['GetAppInfo']();
6+
return window['go']['main']['App']['GetAppInfo']();
77
}
88

99
export function Greet(arg1) {
10-
return window['go']['main']['App']['Greet'](arg1);
10+
return window['go']['main']['App']['Greet'](arg1);
1111
}
1212

1313
export function SendTestEvent() {
14-
return window['go']['main']['App']['SendTestEvent']();
14+
return window['go']['main']['App']['SendTestEvent']();
1515
}
Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
{
2-
"name": "@wailsapp/runtime",
3-
"version": "2.0.0",
4-
"description": "Wails Javascript runtime library",
5-
"keywords": [
6-
"Go",
7-
"Javascript",
8-
"Wails"
9-
],
10-
"homepage": "https://github.com/wailsapp/wails#readme",
11-
"bugs": {
12-
"url": "https://github.com/wailsapp/wails/issues"
13-
},
14-
"license": "MIT",
15-
"author": "Lea Anthony <lea.anthony@gmail.com>",
16-
"repository": {
17-
"type": "git",
18-
"url": "git+https://github.com/wailsapp/wails.git"
19-
},
20-
"main": "runtime.js",
21-
"types": "runtime.d.ts",
22-
"scripts": {}
2+
"name": "@wailsapp/runtime",
3+
"version": "2.0.0",
4+
"description": "Wails Javascript runtime library",
5+
"main": "runtime.js",
6+
"types": "runtime.d.ts",
7+
"scripts": {
8+
},
9+
"repository": {
10+
"type": "git",
11+
"url": "git+https://github.com/wailsapp/wails.git"
12+
},
13+
"keywords": [
14+
"Wails",
15+
"Javascript",
16+
"Go"
17+
],
18+
"author": "Lea Anthony <lea.anthony@gmail.com>",
19+
"license": "MIT",
20+
"bugs": {
21+
"url": "https://github.com/wailsapp/wails/issues"
22+
},
23+
"homepage": "https://github.com/wailsapp/wails#readme"
2324
}

frontend/wailsjs/runtime/runtime.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export function EventsOff(eventName, ...additionalEventNames) {
4949
}
5050

5151
export function EventsOffAll() {
52-
return window.runtime.EventsOffAll();
52+
return window.runtime.EventsOffAll();
5353
}
5454

5555
export function EventsOnce(eventName, callback) {
@@ -239,4 +239,4 @@ export function CanResolveFilePaths() {
239239

240240
export function ResolveFilePaths(files) {
241241
return window.runtime.ResolveFilePaths(files);
242-
}
242+
}

internal/config/config.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package config
2+
3+
type WindowState struct {
4+
X int `json:"x"`
5+
Y int `json:"y"`
6+
Width int `json:"width"`
7+
Height int `json:"height"`
8+
IsClickThrough bool `json:"is_click_through"`
9+
IsTransparent bool `json:"is_transparent"`
10+
Theme string `json:"theme"` // "dark" | "light"
11+
}
12+
13+
type MacOptions struct {
14+
QuitOnClose bool `json:"quit_on_close"`
15+
HideDockIcon bool `json:"hide_dock_icon"`
16+
}
17+
18+
type General struct {
19+
Language string `json:"language"` // default "en-US"
20+
MacOptions MacOptions `json:"mac_options"`
21+
}
22+
23+
type VanishKeybind struct {
24+
Keybind string `json:"keybind"` // empty by default
25+
ActivationMessage string `json:"activation_message"`
26+
}
27+
28+
type Keybinds struct {
29+
Vanish VanishKeybind `json:"vanish"`
30+
}
31+
32+
type TwitchConfig struct {
33+
Channel string `json:"channel"`
34+
DefaultChannel string `json:"default_channel"`
35+
Fade bool `json:"fade"`
36+
FadeTimeout int `json:"fade_timeout"`
37+
Bots bool `json:"bots"`
38+
HideCommands bool `json:"hide_commands"`
39+
HideBadges bool `json:"hide_badges"`
40+
UserBlacklist []string `json:"user_blacklist"`
41+
}
42+
43+
type YouTubeConfig struct {
44+
ChannelID string `json:"channel_id"`
45+
DefaultChannelID string `json:"default_channel_id"`
46+
VideoURL string `json:"video_url"`
47+
Retries int `json:"retries"`
48+
FetchDelay int `json:"fetch_delay"`
49+
UserBlacklist []string `json:"user_blacklist"`
50+
}
51+
52+
type ExternalConfig struct {
53+
DefaultURL string `json:"default_url"`
54+
Sources []string `json:"sources"`
55+
}
56+
57+
type Config struct {
58+
Version string `json:"version"`
59+
WindowState WindowState `json:"window_state"`
60+
General General `json:"general"`
61+
Keybinds Keybinds `json:"keybinds"`
62+
Twitch TwitchConfig `json:"twitch"`
63+
YouTube YouTubeConfig `json:"youtube"`
64+
External ExternalConfig `json:"external"`
65+
}
66+
67+
func DefaultConfig() Config {
68+
return Config{
69+
Version: "1.0.0",
70+
WindowState: WindowState{
71+
Width: 400,
72+
Height: 600,
73+
Theme: "dark",
74+
},
75+
General: General{
76+
Language: "en-US",
77+
MacOptions: MacOptions{},
78+
},
79+
Keybinds: Keybinds{
80+
Vanish: VanishKeybind{
81+
ActivationMessage: "VanishKeybind triggered",
82+
},
83+
},
84+
Twitch: TwitchConfig{
85+
FadeTimeout: 30,
86+
},
87+
YouTube: YouTubeConfig{
88+
Retries: 50,
89+
FetchDelay: 5,
90+
},
91+
External: ExternalConfig{},
92+
}
93+
}

internal/config/store.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package config
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"os"
7+
"path/filepath"
8+
)
9+
10+
const (
11+
AppName = "ghost-chat"
12+
ConfigFileName = "config.json"
13+
)
14+
15+
func GetConfigPath() (string, error) {
16+
userConfigDir, err := os.UserConfigDir()
17+
18+
if err != nil {
19+
return "", err
20+
}
21+
22+
return filepath.Join(userConfigDir, AppName, ConfigFileName), nil
23+
}
24+
25+
func Load(path string) (*Config, error) {
26+
bytes, err := os.ReadFile(path)
27+
28+
if errors.Is(err, os.ErrNotExist) {
29+
defaultConfig := DefaultConfig()
30+
31+
return &defaultConfig, nil
32+
}
33+
34+
if err != nil {
35+
return nil, err
36+
}
37+
38+
var config Config
39+
40+
if err = json.Unmarshal(bytes, &config); err != nil {
41+
return nil, err
42+
}
43+
44+
return &config, nil
45+
}
46+
47+
func Save(config *Config, path string) error {
48+
bytes, err := json.MarshalIndent(config, "", " ")
49+
50+
if err != nil {
51+
return err
52+
}
53+
54+
if err = os.MkdirAll(filepath.Dir(path), 0755); err != nil {
55+
return err
56+
}
57+
58+
return os.WriteFile(path, bytes, 0644)
59+
}

0 commit comments

Comments
 (0)