feat: show spent time in participant list, fix timer sounds
This commit is contained in:
@@ -162,7 +162,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function playSound(name) {
|
async function playSound(name) {
|
||||||
|
// Ensure AudioContext is running (may be suspended after inactivity)
|
||||||
|
const ctx = getAudioContext()
|
||||||
|
if (ctx.state === 'suspended') {
|
||||||
|
await ctx.resume()
|
||||||
|
}
|
||||||
|
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case 'warning':
|
case 'warning':
|
||||||
playBeep(880, 0.15)
|
playBeep(880, 0.15)
|
||||||
|
|||||||
@@ -8,10 +8,17 @@
|
|||||||
|
|
||||||
$: allSpeakers = timerState?.allSpeakers || []
|
$: allSpeakers = timerState?.allSpeakers || []
|
||||||
$: currentSpeakerId = timerState?.currentSpeakerId || 0
|
$: currentSpeakerId = timerState?.currentSpeakerId || 0
|
||||||
|
$: currentElapsed = timerState?.speakerElapsed || 0
|
||||||
|
|
||||||
function handleSkip(speakerId) {
|
function handleSkip(speakerId) {
|
||||||
dispatch('skip', { speakerId })
|
dispatch('skip', { speakerId })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatTime(seconds) {
|
||||||
|
const mins = Math.floor(seconds / 60)
|
||||||
|
const secs = seconds % 60
|
||||||
|
return `${mins}:${secs.toString().padStart(2, '0')}`
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="participant-list">
|
<div class="participant-list">
|
||||||
@@ -23,7 +30,23 @@
|
|||||||
<li class="speaker-item {speaker.status}">
|
<li class="speaker-item {speaker.status}">
|
||||||
<span class="order">{speaker.order}</span>
|
<span class="order">{speaker.order}</span>
|
||||||
<span class="name">{speaker.name}</span>
|
<span class="name">{speaker.name}</span>
|
||||||
<span class="time-limit">{Math.floor(speaker.timeLimit / 60)}:{(speaker.timeLimit % 60).toString().padStart(2, '0')}</span>
|
<span class="time-display">
|
||||||
|
{#if speaker.status === 'done'}
|
||||||
|
<span class="time-spent" class:overtime={speaker.timeSpent > speaker.timeLimit}>
|
||||||
|
{formatTime(speaker.timeSpent)}
|
||||||
|
</span>
|
||||||
|
<span class="time-sep">/</span>
|
||||||
|
<span class="time-limit">{formatTime(speaker.timeLimit)}</span>
|
||||||
|
{:else if speaker.status === 'speaking'}
|
||||||
|
<span class="time-spent" class:overtime={currentElapsed > speaker.timeLimit}>
|
||||||
|
{formatTime(currentElapsed)}
|
||||||
|
</span>
|
||||||
|
<span class="time-sep">/</span>
|
||||||
|
<span class="time-limit">{formatTime(speaker.timeLimit)}</span>
|
||||||
|
{:else}
|
||||||
|
<span class="time-limit">{formatTime(speaker.timeLimit)}</span>
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
{#if speaker.status === 'pending' || speaker.status === 'skipped'}
|
{#if speaker.status === 'pending' || speaker.status === 'skipped'}
|
||||||
<button class="skip-btn" on:click={() => handleSkip(speaker.id)} title="{$t('controls.skip')}">
|
<button class="skip-btn" on:click={() => handleSkip(speaker.id)} title="{$t('controls.skip')}">
|
||||||
⏭
|
⏭
|
||||||
@@ -129,13 +152,6 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-limit {
|
|
||||||
font-family: 'SF Mono', 'Menlo', monospace;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #6b7280;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skip-btn {
|
.skip-btn {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
@@ -163,4 +179,29 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.time-display {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
font-family: 'SF Mono', 'Menlo', monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-spent {
|
||||||
|
color: #4ade80;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-spent.overtime {
|
||||||
|
color: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-sep {
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-limit {
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ type SpeakerInfo struct {
|
|||||||
ID uint `json:"id"`
|
ID uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
TimeLimit int `json:"timeLimit"`
|
TimeLimit int `json:"timeLimit"`
|
||||||
|
TimeSpent int `json:"timeSpent"`
|
||||||
Order int `json:"order"`
|
Order int `json:"order"`
|
||||||
Status SpeakerStatus `json:"status"`
|
Status SpeakerStatus `json:"status"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,12 +118,14 @@ func (t *Timer) startNextSpeaker(now time.Time) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark previous speaker as done (only if they were speaking, not skipped)
|
// Mark previous speaker as done and save their time spent
|
||||||
if t.currentSpeakerID != 0 {
|
if t.currentSpeakerID != 0 {
|
||||||
|
timeSpent := int(now.Sub(t.speakerStartTime).Seconds())
|
||||||
for i := range t.allSpeakers {
|
for i := range t.allSpeakers {
|
||||||
if t.allSpeakers[i].ID == t.currentSpeakerID {
|
if t.allSpeakers[i].ID == t.currentSpeakerID {
|
||||||
if t.allSpeakers[i].Status == models.SpeakerStatusSpeaking {
|
if t.allSpeakers[i].Status == models.SpeakerStatusSpeaking {
|
||||||
t.allSpeakers[i].Status = models.SpeakerStatusDone
|
t.allSpeakers[i].Status = models.SpeakerStatusDone
|
||||||
|
t.allSpeakers[i].TimeSpent = timeSpent
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -299,9 +301,17 @@ func (t *Timer) Stop() {
|
|||||||
t.mu.Unlock()
|
t.mu.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Mark current speaker as done before stopping
|
// Mark current speaker as done and save their time spent
|
||||||
if t.currentSpeakerID != 0 {
|
if t.currentSpeakerID != 0 {
|
||||||
t.updateSpeakerStatus(t.currentSpeakerID, models.SpeakerStatusDone)
|
now := time.Now()
|
||||||
|
timeSpent := int(now.Sub(t.speakerStartTime).Seconds())
|
||||||
|
for i := range t.allSpeakers {
|
||||||
|
if t.allSpeakers[i].ID == t.currentSpeakerID {
|
||||||
|
t.allSpeakers[i].Status = models.SpeakerStatusDone
|
||||||
|
t.allSpeakers[i].TimeSpent = timeSpent
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
t.running = false
|
t.running = false
|
||||||
t.paused = false
|
t.paused = false
|
||||||
|
|||||||
Reference in New Issue
Block a user