Enhance content management and user interaction features
- Introduced a new content source toggle in the profile and app header to switch between IndeeHub and TopDoc films. - Updated the content fetching logic to dynamically load content based on the selected source. - Enhanced the seeding process to include a combined catalog of IndeeHub and TopDoc films, ensuring diverse content availability. - Improved user interaction by preventing duplicate reactions and ensuring a smoother voting experience across comments and content. - Added support for Amber login (NIP-55) for Android users, integrating it into the existing authentication flow. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -9,132 +9,168 @@
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Video Area (Dummy Player) -->
|
||||
<div class="video-area">
|
||||
<img
|
||||
v-if="content?.backdrop || content?.thumbnail"
|
||||
:src="content?.backdrop || content?.thumbnail"
|
||||
:alt="content?.title"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
|
||||
<!-- Play Overlay -->
|
||||
<div class="video-overlay">
|
||||
<button class="play-button" @click="togglePlay">
|
||||
<svg v-if="!isPlaying" class="w-20 h-20" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M8 5v14l11-7z" />
|
||||
</svg>
|
||||
<svg v-else class="w-20 h-20" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z" />
|
||||
</svg>
|
||||
</button>
|
||||
<!-- ── Real YouTube Embed ── -->
|
||||
<template v-if="embedUrl">
|
||||
<div class="video-area iframe-area">
|
||||
<iframe
|
||||
:src="embedUrl"
|
||||
class="w-full h-full"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
referrerpolicy="strict-origin-when-cross-origin"
|
||||
allowfullscreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
<!-- Dummy Notice -->
|
||||
<div class="demo-notice">
|
||||
<div class="demo-badge">
|
||||
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />
|
||||
<!-- Minimal info bar -->
|
||||
<div class="stream-info-bar">
|
||||
<div class="flex items-center gap-4 text-sm">
|
||||
<span class="text-white font-medium">{{ content?.title }}</span>
|
||||
<span v-if="content?.releaseYear" class="text-white/50">{{ content.releaseYear }}</span>
|
||||
<span v-if="content?.duration" class="text-white/50">{{ content.duration }} min</span>
|
||||
</div>
|
||||
<a
|
||||
:href="content?.streamingUrl?.replace('/embed/', '/watch?v=') ?? '#'"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="yt-link"
|
||||
>
|
||||
Open on YouTube
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
||||
</svg>
|
||||
Demo Mode
|
||||
</div>
|
||||
<p class="text-sm">Video player preview - Full streaming coming soon</p>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Video Controls -->
|
||||
<div class="video-controls">
|
||||
<!-- Progress Bar -->
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-filled" :style="{ width: `${progress}%` }"></div>
|
||||
<div class="progress-handle" :style="{ left: `${progress}%` }"></div>
|
||||
</div>
|
||||
<div class="time-display">
|
||||
<span>{{ formatTime(currentTime) }}</span>
|
||||
<span class="text-white/60">/</span>
|
||||
<span class="text-white/60">{{ formatTime(duration) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Control Buttons -->
|
||||
<div class="control-buttons">
|
||||
<!-- Left Side -->
|
||||
<div class="flex items-center gap-4">
|
||||
<button @click="togglePlay" class="control-btn">
|
||||
<svg v-if="!isPlaying" class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
|
||||
<!-- ── Demo Player (no streaming URL) ── -->
|
||||
<template v-else>
|
||||
<div class="video-area">
|
||||
<img
|
||||
v-if="content?.backdrop || content?.thumbnail"
|
||||
:src="content?.backdrop || content?.thumbnail"
|
||||
:alt="content?.title"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
|
||||
<!-- Play Overlay -->
|
||||
<div class="video-overlay">
|
||||
<button class="play-button" @click="togglePlay">
|
||||
<svg v-if="!isPlaying" class="w-20 h-20" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M8 5v14l11-7z" />
|
||||
</svg>
|
||||
<svg v-else class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
|
||||
<svg v-else class="w-20 h-20" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button class="control-btn">
|
||||
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M5 4v16l7-8z M12 4v16l7-8z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button class="control-btn">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.536 8.464a5 5 0 010 7.072m2.828-9.9a9 9 0 010 12.728M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="text-white font-medium text-sm">{{ content?.title }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Side -->
|
||||
<div class="flex items-center gap-4">
|
||||
<button class="control-btn">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<!-- Demo Notice -->
|
||||
<div class="demo-notice">
|
||||
<div class="demo-badge">
|
||||
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
Demo Mode
|
||||
</div>
|
||||
<p class="text-sm">Video player preview - Full streaming coming soon</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Video Controls -->
|
||||
<div class="video-controls">
|
||||
<!-- Progress Bar -->
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-filled" :style="{ width: `${progress}%` }"></div>
|
||||
<div class="progress-handle" :style="{ left: `${progress}%` }"></div>
|
||||
</div>
|
||||
<div class="time-display">
|
||||
<span>{{ formatTime(currentTime) }}</span>
|
||||
<span class="text-white/60">/</span>
|
||||
<span class="text-white/60">{{ formatTime(duration) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Control Buttons -->
|
||||
<div class="control-buttons">
|
||||
<!-- Left Side -->
|
||||
<div class="flex items-center gap-4">
|
||||
<button @click="togglePlay" class="control-btn">
|
||||
<svg v-if="!isPlaying" class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M8 5v14l11-7z" />
|
||||
</svg>
|
||||
<svg v-else class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="quality-selector">
|
||||
<button class="control-btn">
|
||||
{{ quality }}
|
||||
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M5 4v16l7-8z M12 4v16l7-8z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button class="control-btn">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.536 8.464a5 5 0 010 7.072m2.828-9.9a9 9 0 010 12.728M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="text-white font-medium text-sm">{{ content?.title }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Side -->
|
||||
<div class="flex items-center gap-4">
|
||||
<button class="control-btn">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="quality-selector">
|
||||
<button class="control-btn">
|
||||
{{ quality }}
|
||||
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button class="control-btn" @click="toggleFullscreen">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button class="control-btn" @click="toggleFullscreen">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content Info Panel -->
|
||||
<div class="content-info-panel">
|
||||
<h2 class="text-2xl font-bold text-white mb-2">{{ content?.title }}</h2>
|
||||
<div class="flex items-center gap-4 text-sm text-white/80 mb-4">
|
||||
<span v-if="content?.releaseYear" class="bg-white/10 px-2 py-0.5 rounded">{{ content.releaseYear }}</span>
|
||||
<span v-if="content?.rating">{{ content.rating }}</span>
|
||||
<span v-if="content?.duration">{{ content.duration }}min</span>
|
||||
<span class="text-green-400 flex items-center gap-1">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
Cinephile Access
|
||||
</span>
|
||||
<!-- Content Info Panel -->
|
||||
<div class="content-info-panel">
|
||||
<h2 class="text-2xl font-bold text-white mb-2">{{ content?.title }}</h2>
|
||||
<div class="flex items-center gap-4 text-sm text-white/80 mb-4">
|
||||
<span v-if="content?.releaseYear" class="bg-white/10 px-2 py-0.5 rounded">{{ content.releaseYear }}</span>
|
||||
<span v-if="content?.rating">{{ content.rating }}</span>
|
||||
<span v-if="content?.duration">{{ content.duration }}min</span>
|
||||
<span class="text-green-400 flex items-center gap-1">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
Cinephile Access
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-white/70 text-sm line-clamp-3">{{ content?.description }}</p>
|
||||
</div>
|
||||
<p class="text-white/70 text-sm line-clamp-3">{{ content?.description }}</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import type { Content } from '../types/content'
|
||||
|
||||
interface Props {
|
||||
@@ -157,6 +193,17 @@ const quality = ref('4K')
|
||||
|
||||
let playInterval: number | null = null
|
||||
|
||||
/**
|
||||
* Build the YouTube embed URL with autoplay when the player opens.
|
||||
* Returns null when the content has no streamingUrl.
|
||||
*/
|
||||
const embedUrl = computed(() => {
|
||||
if (!props.content?.streamingUrl) return null
|
||||
const base = props.content.streamingUrl
|
||||
const sep = base.includes('?') ? '&' : '?'
|
||||
return `${base}${sep}autoplay=1&rel=0&modestbranding=1`
|
||||
})
|
||||
|
||||
watch(() => props.isOpen, (newVal) => {
|
||||
if (newVal) {
|
||||
// Reset player state when opened
|
||||
@@ -184,7 +231,6 @@ function togglePlay() {
|
||||
|
||||
function startPlay() {
|
||||
isPlaying.value = true
|
||||
console.log('▶️ Video playing (demo mode)')
|
||||
|
||||
// Simulate playback progress
|
||||
playInterval = window.setInterval(() => {
|
||||
@@ -208,7 +254,13 @@ function stopPlay() {
|
||||
}
|
||||
|
||||
function toggleFullscreen() {
|
||||
console.log('🖥️ Fullscreen toggled (demo)')
|
||||
const el = document.querySelector('.video-player-overlay')
|
||||
if (!el) return
|
||||
if (document.fullscreenElement) {
|
||||
document.exitFullscreen()
|
||||
} else {
|
||||
el.requestFullscreen()
|
||||
}
|
||||
}
|
||||
|
||||
function formatTime(seconds: number): string {
|
||||
@@ -275,6 +327,42 @@ function formatTime(seconds: number): string {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* YouTube iframe fills the entire area */
|
||||
.iframe-area iframe {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Minimal info bar for YouTube embed mode */
|
||||
.stream-info-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 24px;
|
||||
background: rgba(0, 0, 0, 0.85);
|
||||
backdrop-filter: blur(24px);
|
||||
-webkit-backdrop-filter: blur(24px);
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.yt-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.yt-link:hover {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.video-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
@@ -477,5 +565,11 @@ function formatTime(seconds: number): string {
|
||||
.control-buttons .text-white {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.stream-info-bar {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user