feat: add Jira URL integration and relay functionality

- Implemented OpenBrowserURL function to open Jira links
- Updated components to utilize the new Jira URL feature
- Added relay server to manage real-time URL updates
- Set default Jira URL in settings if not specified
This commit is contained in:
Mikhail Kiselev
2026-04-03 03:31:00 +03:00
parent 79214910f1
commit 41bc35fd43
9 changed files with 85 additions and 43 deletions

View File

@@ -21,7 +21,7 @@ const (
)
type Event struct {
Type EventType `json:"type"`
Type EventType `json:"type"`
State models.TimerState `json:"state"`
}
@@ -31,29 +31,29 @@ type Timer struct {
running bool
paused bool
meetingStartTime time.Time
meetingElapsed time.Duration
meetingLimit time.Duration
meetingStartTime time.Time
meetingElapsed time.Duration
meetingLimit time.Duration
speakerStartTime time.Time
speakerElapsed time.Duration
speakerLimit time.Duration
speakerStartTime time.Time
speakerElapsed time.Duration
speakerLimit time.Duration
currentSpeakerID uint
currentSpeaker string
speakingOrder int
queue []models.QueuedSpeaker
allSpeakers []models.SpeakerInfo
currentSpeakerID uint
currentSpeaker string
speakingOrder int
queue []models.QueuedSpeaker
allSpeakers []models.SpeakerInfo
warningThreshold time.Duration
speakerWarned bool
warningThreshold time.Duration
speakerWarned bool
speakerTimeUpEmitted bool
meetingWarned bool
meetingWarned bool
eventCh chan Event
ctx context.Context
cancel context.CancelFunc
pausedAt time.Time
eventCh chan Event
ctx context.Context
cancel context.CancelFunc
pausedAt time.Time
}
func New(meetingLimitSec, warningThresholdSec int) *Timer {
@@ -75,7 +75,7 @@ func (t *Timer) SetQueue(speakers []models.QueuedSpeaker) {
t.mu.Lock()
defer t.mu.Unlock()
t.queue = speakers
// Initialize allSpeakers with pending status
t.allSpeakers = make([]models.SpeakerInfo, len(speakers))
for i, s := range speakers {
@@ -105,16 +105,8 @@ func (t *Timer) Start() {
t.speakerWarned = false
t.meetingWarned = false
hasSpeakers := len(t.queue) > 0
if hasSpeakers {
t.startNextSpeaker(now, 0)
}
t.mu.Unlock()
if hasSpeakers {
t.emit(EventSpeakerChanged)
}
// Не активируем участника автоматически!
go t.tick()
}
@@ -461,7 +453,9 @@ func (t *Timer) buildState() models.TimerState {
if t.running && !t.paused {
now := time.Now()
speakerElapsed = now.Sub(t.speakerStartTime)
if t.currentSpeakerID != 0 {
speakerElapsed = now.Sub(t.speakerStartTime)
}
meetingElapsed = now.Sub(t.meetingStartTime)
}
@@ -472,7 +466,7 @@ func (t *Timer) buildState() models.TimerState {
// Copy allSpeakers to avoid data race and calculate total speakers time
allSpeakers := make([]models.SpeakerInfo, len(t.allSpeakers))
copy(allSpeakers, t.allSpeakers)
totalSpeakersTime := 0
for _, s := range t.allSpeakers {
totalSpeakersTime += s.TimeLimit
@@ -519,9 +513,15 @@ func (t *Timer) tick() {
}
now := time.Now()
speakerElapsed := now.Sub(t.speakerStartTime)
meetingElapsed := now.Sub(t.meetingStartTime)
if t.currentSpeakerID == 0 {
t.mu.Unlock()
t.emit(EventTick)
continue
}
speakerElapsed := now.Sub(t.speakerStartTime)
remaining := t.speakerLimit - speakerElapsed
if !t.speakerWarned && remaining <= t.warningThreshold && remaining > 0 {
t.speakerWarned = true