mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
feat: implement full client UI from mockup — 10 phases, 331 tests
Client UI: - Design system: Colors, Typography, Controls resource dictionaries - Message actions: reply compose bar, hover edit/delete/reply buttons - Rich content: code blocks, attachments, system messages, content parser - Server strip: 72px sidebar with server icons, home button, add server - Status picker: popup for changing online/idle/dnd/invisible status - ConnectPage: server health check dots with auto-refresh - User popup: profile card with banner, avatar, roles, member since - Emoji picker: 6 categories, search, grid of Unicode emojis - Settings overlay: full-screen with sidebar navigation - Friends/DM view: sidebar + friends list with tabs (online/all/pending) - Toast notifications: auto-dismiss after 3s with fade animation Models & services: - Attachment model added to Message, ApiMessage, ChatMessagePayload - EditMessageAsync, DeleteMessageAsync, SendStatusChangeAsync APIs - MessageContentParser (code blocks, inline code, bold, italic) - EmojiData, ToastService, HealthStatusToBrushConverter Server (from prior session): - Voice room management, SFU, speaker detection - ACME/TLS support, config improvements - Protocol and schema updates Tests: 331 passing (61 converter + 24 voice service + 34 voice VM + 41 parser + 9 edit/delete + existing)
This commit is contained in:
+19
-8
@@ -29,12 +29,18 @@ type GitHubConfig struct {
|
||||
Token string `koanf:"token"`
|
||||
}
|
||||
|
||||
// VoiceConfig holds STUN/TURN server settings for WebRTC signaling.
|
||||
// VoiceConfig holds STUN/TURN server settings and SFU configuration.
|
||||
type VoiceConfig struct {
|
||||
TURNSecret string `koanf:"turn_secret"` // HMAC-SHA1 secret; auto-generated if empty
|
||||
STUNPort int `koanf:"stun_port"` // default 3478
|
||||
TURNPort int `koanf:"turn_port"` // default 3478
|
||||
TURNEnabled bool `koanf:"turn_enabled"` // default true
|
||||
TURNSecret string `koanf:"turn_secret"` // HMAC-SHA1 secret; auto-generated if empty
|
||||
STUNPort int `koanf:"stun_port"` // default 3478
|
||||
TURNPort int `koanf:"turn_port"` // default 3478
|
||||
TURNEnabled bool `koanf:"turn_enabled"` // default true
|
||||
Quality string `koanf:"quality"` // low | medium | high
|
||||
MixingThreshold int `koanf:"mixing_threshold"` // selective forwarding threshold
|
||||
TopSpeakers int `koanf:"top_speakers"` // top-N speakers in selective mode
|
||||
ExternalIP string `koanf:"external_ip"` // set if behind NAT
|
||||
MediaPortMin int `koanf:"media_port_min"` // UDP port range start for WebRTC media
|
||||
MediaPortMax int `koanf:"media_port_max"` // UDP port range end for WebRTC media
|
||||
}
|
||||
|
||||
// ServerConfig holds HTTP server settings.
|
||||
@@ -90,9 +96,14 @@ func defaults() Config {
|
||||
StorageDir: "data/uploads",
|
||||
},
|
||||
Voice: VoiceConfig{
|
||||
STUNPort: 3478,
|
||||
TURNPort: 3478,
|
||||
TURNEnabled: true,
|
||||
STUNPort: 3478,
|
||||
TURNPort: 3478,
|
||||
TURNEnabled: true,
|
||||
Quality: "medium",
|
||||
MixingThreshold: 10,
|
||||
TopSpeakers: 3,
|
||||
MediaPortMin: 10000,
|
||||
MediaPortMax: 10100,
|
||||
},
|
||||
GitHub: GitHubConfig{},
|
||||
}
|
||||
|
||||
@@ -226,6 +226,82 @@ tls:
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadVoiceConfigDefaults(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
cfgPath := filepath.Join(tmpDir, "config.yaml")
|
||||
|
||||
cfg, err := config.Load(cfgPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Load() returned error: %v", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
got interface{}
|
||||
want interface{}
|
||||
}{
|
||||
{"Voice.Quality", cfg.Voice.Quality, "medium"},
|
||||
{"Voice.MixingThreshold", cfg.Voice.MixingThreshold, 10},
|
||||
{"Voice.TopSpeakers", cfg.Voice.TopSpeakers, 3},
|
||||
{"Voice.ExternalIP", cfg.Voice.ExternalIP, ""},
|
||||
{"Voice.MediaPortMin", cfg.Voice.MediaPortMin, 10000},
|
||||
{"Voice.MediaPortMax", cfg.Voice.MediaPortMax, 10100},
|
||||
{"Voice.STUNPort", cfg.Voice.STUNPort, 3478},
|
||||
{"Voice.TURNPort", cfg.Voice.TURNPort, 3478},
|
||||
{"Voice.TURNEnabled", cfg.Voice.TURNEnabled, true},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if tc.got != tc.want {
|
||||
t.Errorf("got %v, want %v", tc.got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadVoiceConfigFromYAML(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
cfgPath := filepath.Join(tmpDir, "config.yaml")
|
||||
|
||||
yaml := `
|
||||
voice:
|
||||
quality: high
|
||||
mixing_threshold: 5
|
||||
top_speakers: 4
|
||||
external_ip: "1.2.3.4"
|
||||
media_port_min: 20000
|
||||
media_port_max: 20500
|
||||
`
|
||||
if err := os.WriteFile(cfgPath, []byte(yaml), 0o644); err != nil {
|
||||
t.Fatalf("failed to write yaml: %v", err)
|
||||
}
|
||||
|
||||
cfg, err := config.Load(cfgPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Load() returned error: %v", err)
|
||||
}
|
||||
|
||||
if cfg.Voice.Quality != "high" {
|
||||
t.Errorf("Voice.Quality = %q, want 'high'", cfg.Voice.Quality)
|
||||
}
|
||||
if cfg.Voice.MixingThreshold != 5 {
|
||||
t.Errorf("Voice.MixingThreshold = %d, want 5", cfg.Voice.MixingThreshold)
|
||||
}
|
||||
if cfg.Voice.TopSpeakers != 4 {
|
||||
t.Errorf("Voice.TopSpeakers = %d, want 4", cfg.Voice.TopSpeakers)
|
||||
}
|
||||
if cfg.Voice.ExternalIP != "1.2.3.4" {
|
||||
t.Errorf("Voice.ExternalIP = %q, want '1.2.3.4'", cfg.Voice.ExternalIP)
|
||||
}
|
||||
if cfg.Voice.MediaPortMin != 20000 {
|
||||
t.Errorf("Voice.MediaPortMin = %d, want 20000", cfg.Voice.MediaPortMin)
|
||||
}
|
||||
if cfg.Voice.MediaPortMax != 20500 {
|
||||
t.Errorf("Voice.MediaPortMax = %d, want 20500", cfg.Voice.MediaPortMax)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadUploadBoundaryValues(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
cfgPath := filepath.Join(tmpDir, "config.yaml")
|
||||
|
||||
Reference in New Issue
Block a user