Files
dictia-public/templates/components/detail/desktop-right-panel.html

273 lines
18 KiB
HTML

<!-- Desktop Right Panel (Summary/Notes/Chat) -->
<div id="rightMainColumn" class="flex flex-col overflow-hidden" :style="{width: rightColumnWidth + '%'}">
<!-- Custom Audio Player -->
<div class="px-4 py-3 border-b border-[var(--border-primary)] flex-shrink-0">
<!-- Show message if audio has been deleted -->
<div v-if="selectedRecording.audio_deleted_at"
class="text-[var(--text-muted)] text-sm flex items-center gap-2">
<i class="fas fa-info-circle"></i>
<span v-text="t('help.audioDeletedMessage')"></span>
</div>
<!-- Show message for incognito recordings (no audio stored) -->
<div v-else-if="selectedRecording.incognito"
class="text-[var(--text-muted)] text-sm flex items-center gap-2 py-2">
<i class="fas fa-user-secret"></i>
<span v-text="t('incognito.audioNotStored')"></span>
</div>
<!-- Custom Audio Player Card -->
<div v-else class="bg-[var(--bg-tertiary)] rounded-xl p-4 shadow-sm border border-[var(--border-primary)] overflow-hidden">
<!-- Video/Audio element wrapped in Teleport for fullscreen -->
<Teleport to="body" :disabled="!videoFullscreen">
<div :class="videoFullscreen ? 'video-fullscreen-overlay' : ''"
@mousemove="videoFullscreen && handleFullscreenMouseMove()"
@click.self="videoFullscreen && toggleAudioPlayback()">
<component :is="selectedRecording.mime_type && selectedRecording.mime_type.startsWith('video/') ? 'video' : 'audio'"
ref="audioPlayerElement"
:src="'/audio/' + selectedRecording.id"
:volume="playerVolume"
@play="handleAudioPlayPause"
@pause="handleAudioPlayPause"
@timeupdate="handleCustomAudioTimeUpdate"
@loadedmetadata="handleAudioLoadedMetadata"
@durationchange="handleAudioDurationChange"
@ended="handleAudioEnded"
@waiting="handleAudioWaiting"
@canplay="handleAudioCanPlay"
@click="selectedRecording.mime_type && selectedRecording.mime_type.startsWith('video/') && toggleAudioPlayback()"
@dblclick="selectedRecording.mime_type && selectedRecording.mime_type.startsWith('video/') && !videoFullscreen && enterVideoFullscreen()"
:class="selectedRecording.mime_type && selectedRecording.mime_type.startsWith('video/')
? (videoFullscreen ? 'video-fullscreen-video' : (videoCollapsed ? 'hidden' : 'w-full rounded-lg mb-3 cursor-pointer'))
: 'hidden'">
</component>
<!-- Fullscreen Subtitle Overlay -->
<div v-if="videoFullscreen && currentSubtitle"
class="video-fullscreen-subtitle"
:class="{ 'subtitle-shifted': fullscreenControlsVisible }">
<span v-if="currentSubtitle.speaker"
class="video-fullscreen-subtitle-speaker"
:style="{ color: 'var(--' + currentSubtitle.color + ')' }">${ currentSubtitle.speaker }:</span>
<span>${ currentSubtitle.text }</span>
</div>
<!-- Fullscreen Control Bar -->
<div v-if="videoFullscreen"
class="video-fullscreen-controls"
:class="{ visible: fullscreenControlsVisible }"
@mousemove.stop="handleFullscreenMouseMove()">
<!-- Progress Bar -->
<div class="px-4 mb-2">
<div class="w-full h-5 rounded-full cursor-pointer relative group flex items-center"
@mousedown="startProgressDrag"
@touchstart.prevent="startProgressDrag">
<div class="progress-track w-full h-1 rounded-full relative bg-white/30">
<div class="h-full bg-white rounded-full pointer-events-none"
:style="{ width: audioProgressPercent + '%' }"></div>
</div>
<div class="absolute top-1/2 w-3 h-3 bg-white rounded-full shadow-md transition-transform group-hover:scale-125 pointer-events-none -translate-y-1/2"
:style="{ left: 'clamp(0px, calc(' + audioProgressPercent + '% - 6px), calc(100% - 12px))' }"></div>
</div>
</div>
<!-- Controls Row -->
<div class="flex items-center gap-3 px-4 pb-4">
<!-- Play/Pause -->
<button @click.stop="toggleAudioPlayback"
class="w-10 h-10 flex items-center justify-center rounded-full text-white hover:bg-white/20 transition-all">
<i :class="audioIsPlaying ? 'fas fa-pause' : 'fas fa-play'" class="text-lg" :style="!audioIsPlaying ? 'margin-left: 2px' : ''"></i>
</button>
<!-- Volume -->
<div class="flex items-center gap-2">
<button @click.stop="toggleAudioMute"
class="w-8 h-8 flex items-center justify-center rounded-full text-white hover:bg-white/20 transition-all">
<i :class="audioIsMuted || playerVolume === 0 ? 'fas fa-volume-mute' : playerVolume < 0.5 ? 'fas fa-volume-down' : 'fas fa-volume-up'" class="text-sm"></i>
</button>
<input type="range" min="0" max="1" step="0.05"
:value="audioIsMuted ? 0 : playerVolume"
@input="(e) => setAudioVolume(parseFloat(e.target.value))"
class="fullscreen-volume-slider w-20">
</div>
<!-- Time -->
<div class="flex items-baseline gap-1 text-white">
<span class="text-sm font-mono">${ formatAudioTime(displayCurrentTime) }</span>
<span class="text-xs opacity-60">/</span>
<span class="text-xs opacity-60 font-mono">${ formatAudioTime(audioDuration) }</span>
</div>
<div class="flex-1"></div>
<!-- Speed -->
<button @click.stop="cyclePlaybackRate"
class="px-2 h-8 flex items-center justify-center rounded-lg text-white hover:bg-white/20 transition-all"
title="Playback speed">
<span class="text-xs font-semibold font-mono">${ formatPlaybackRate(playbackRate) }</span>
</button>
<!-- Exit Fullscreen -->
<button @click.stop="exitVideoFullscreen"
class="w-8 h-8 flex items-center justify-center rounded-full text-white hover:bg-white/20 transition-all"
:title="t('tooltips.exitFullscreen')">
<i class="fas fa-compress text-sm"></i>
</button>
</div>
</div>
</div>
</Teleport>
<!-- Normal Controls (hidden when fullscreen) -->
<div v-show="!videoFullscreen">
<!-- Progress Bar (now on top, full width, draggable) -->
<div class="w-full h-4 rounded-full cursor-pointer relative mb-3 group flex items-center"
@mousedown="startProgressDrag">
<!-- Track background -->
<div class="progress-track w-full h-2 rounded-full relative bg-[var(--border-accent)] opacity-40">
<!-- Progress fill -->
<div class="h-full bg-[var(--text-accent)] rounded-full pointer-events-none opacity-100"
:style="{ width: audioProgressPercent + '%' }"></div>
</div>
<!-- Progress dot - stays within track bounds -->
<div class="absolute top-1/2 w-4 h-4 bg-[var(--text-accent)] rounded-full shadow-md transition-transform group-hover:scale-110 pointer-events-none -translate-y-1/2"
:style="{ left: 'clamp(0px, calc(' + audioProgressPercent + '% - 8px), calc(100% - 16px))' }"
style="box-shadow: 0 2px 6px rgba(0,0,0,0.3);"></div>
</div>
<!-- Controls Row -->
<div class="flex items-center gap-2 min-w-0">
<!-- Play/Pause Button -->
<button @click="toggleAudioPlayback"
class="w-10 h-10 flex items-center justify-center rounded-full player-play-button transition-all duration-200 flex-shrink-0 shadow-md hover:shadow-lg hover:scale-105"
:title="audioIsPlaying ? t('tooltips.pause') : t('tooltips.play')">
<i :class="audioIsPlaying ? 'fas fa-pause' : 'fas fa-play'" class="text-base" :style="!audioIsPlaying ? 'margin-left: 2px' : ''"></i>
</button>
<!-- Time Display -->
<div class="flex items-baseline gap-1 flex-shrink-0 min-w-0">
<span class="text-sm font-semibold font-mono" :class="isDraggingProgress ? 'text-[var(--text-accent)]' : 'text-[var(--text-primary)]'">${ formatAudioTime(displayCurrentTime) }</span>
<span class="text-xs text-[var(--text-muted)]">/</span>
<span class="text-xs text-[var(--text-muted)] font-mono">${ formatAudioTime(audioDuration) }</span>
</div>
<!-- Playback Speed Control -->
<div class="relative flex-shrink-0">
<button ref="speedButtonDesktop"
@click="showSpeedMenu = !showSpeedMenu; $nextTick(() => updateSpeedMenuPosition($refs.speedButtonDesktop))"
data-speed-toggle
class="w-8 h-8 flex items-center justify-center rounded-lg hover:bg-[var(--bg-primary)] text-[var(--text-accent)] hover:opacity-80 transition-all"
:title="t('tooltips.playbackSpeed') || 'Playback speed'">
<span class="text-xs font-semibold font-mono">${ formatPlaybackRate(playbackRate) }</span>
</button>
<!-- Dropdown menu (teleported to body, fixed positioning) -->
<Teleport to="body">
<div v-if="showSpeedMenu" @click.stop
data-speed-dropdown
class="fixed bg-[var(--bg-tertiary)] border border-[var(--border-accent)] rounded-md shadow-xl z-[9999] speed-dropdown overflow-y-auto backdrop-blur-sm"
:style="speedMenuPosition">
<div class="py-0.5">
<button v-for="speed in playbackSpeeds" :key="speed"
@mousedown.prevent="setPlaybackRate(speed); showSpeedMenu = false"
class="w-full px-2 py-0.5 text-[11px] font-mono text-left hover:bg-[var(--bg-accent-light)] transition-colors"
:class="speed === playbackRate ? 'text-[var(--text-accent)] font-semibold bg-[var(--bg-accent-light)]' : 'text-[var(--text-primary)]'">
${ speed }x
</button>
</div>
</div>
</Teleport>
</div>
<!-- Spacer -->
<div class="flex-1 min-w-0"></div>
<!-- Volume Control - hidden on very narrow screens -->
<div class="hidden sm:flex items-center gap-1 flex-shrink-0">
<button @click="toggleAudioMute"
class="w-8 h-8 flex items-center justify-center rounded-lg hover:bg-[var(--bg-primary)] hover:opacity-80 transition-all"
:class="audioIsMuted || playerVolume === 0 ? 'text-[var(--text-muted)]' : 'text-[var(--text-accent)]'"
:title="audioIsMuted ? t('tooltips.unmute') : t('tooltips.mute')">
<i :class="audioIsMuted || playerVolume === 0 ? 'fas fa-volume-mute' : playerVolume < 0.5 ? 'fas fa-volume-down' : 'fas fa-volume-up'" class="text-sm"></i>
</button>
<input type="range" min="0" max="1" step="0.05"
:value="audioIsMuted ? 0 : playerVolume"
@input="(e) => setAudioVolume(parseFloat(e.target.value))"
class="volume-slider w-16">
</div>
<!-- Download Button -->
<a :href="'/audio/' + selectedRecording.id + '?download=true'"
download
class="w-8 h-8 flex items-center justify-center rounded-lg hover:bg-[var(--bg-primary)] text-[var(--text-accent)] hover:opacity-80 transition-all flex-shrink-0"
:title="t('buttons.downloadAudio') || 'Download audio'">
<i class="fas fa-download text-sm"></i>
</a>
<!-- Video Fullscreen Button -->
<button v-if="selectedRecording.mime_type && selectedRecording.mime_type.startsWith('video/') && !videoCollapsed"
@click="enterVideoFullscreen"
class="w-8 h-8 flex items-center justify-center rounded-lg hover:bg-[var(--bg-primary)] text-[var(--text-accent)] hover:opacity-80 transition-all flex-shrink-0"
:title="t('tooltips.fullscreenVideo')">
<i class="fas fa-expand text-sm"></i>
</button>
<!-- Video Toggle Button -->
<button v-if="selectedRecording.mime_type && selectedRecording.mime_type.startsWith('video/')"
@click="videoCollapsed = !videoCollapsed"
class="w-8 h-8 flex items-center justify-center rounded-lg hover:bg-[var(--bg-primary)] transition-all flex-shrink-0"
:class="videoCollapsed ? 'text-[var(--text-muted)]' : 'text-[var(--text-accent)]'"
:title="videoCollapsed ? t('tooltips.showVideo') : t('tooltips.hideVideo')">
<i :class="videoCollapsed ? 'fas fa-eye-slash' : 'fas fa-eye'" class="text-sm"></i>
</button>
</div>
</div>
</div>
</div>
<!-- Summary/Notes Tabs -->
<div :class="{'flex-1': !isChatMaximized, 'flex-none': isChatMaximized}" class="flex flex-col overflow-hidden">
<!-- Tab Navigation -->
<div class="bg-[var(--bg-tertiary)] border-b border-[var(--border-primary)] flex">
<button @click="selectedTab = 'summary'"
:class="[
'flex-1 px-4 py-3 text-sm font-medium transition-colors',
selectedTab === 'summary'
? 'bg-[var(--bg-secondary)] text-[var(--text-accent)] border-b-2 border-[var(--border-focus)]'
: 'text-[var(--text-muted)] hover:text-[var(--text-secondary)]'
]">
${ t('summary.title') }
</button>
<button @click="selectedTab = 'notes'"
:class="[
'flex-1 px-4 py-3 text-sm font-medium transition-colors',
selectedTab === 'notes'
? 'bg-[var(--bg-secondary)] text-[var(--text-accent)] border-b-2 border-[var(--border-focus)]'
: 'text-[var(--text-muted)] hover:text-[var(--text-secondary)]'
]">
${ t('notes.title') }
</button>
<button v-if="selectedRecording.events && selectedRecording.events.length > 0"
@click="selectedTab = 'events'"
:class="[
'flex-1 px-4 py-3 text-sm font-medium transition-colors',
selectedTab === 'events'
? 'bg-[var(--bg-secondary)] text-[var(--text-accent)] border-b-2 border-[var(--border-focus)]'
: 'text-[var(--text-muted)] hover:text-[var(--text-secondary)]'
]">
<i class="fas fa-calendar-alt mr-1"></i>
${ t('events.title') } (${ selectedRecording.events.length })
</button>
</div>
<!-- Tab Content -->
<div v-if="!isChatMaximized" class="flex-1 overflow-hidden">
{% include 'components/detail/desktop-summary-tab.html' %}
{% include 'components/detail/desktop-notes-tab.html' %}
{% include 'components/detail/desktop-events-tab.html' %}
</div>
</div>
<!-- Chat Section -->
{% include 'components/detail/desktop-chat-section.html' %}
</div>