vid-project / index.html
frucht's picture
Add 3 files
a53e528 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Workout Video Archiver | Trainer's Toolkit</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
/* Custom styles that can't be done with Tailwind */
.video-upload-area {
border: 2px dashed #cbd5e0;
transition: all 0.3s ease;
}
.video-upload-area:hover {
border-color: #4f46e5;
background-color: #f8fafc;
}
.video-upload-area.dragover {
border-color: #4f46e5;
background-color: #e0e7ff;
}
.video-thumbnail {
transition: transform 0.2s ease;
}
.video-thumbnail:hover {
transform: scale(1.03);
}
.tag-cloud {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.tag-item {
cursor: pointer;
transition: all 0.2s ease;
}
.tag-item:hover {
transform: translateY(-2px);
}
.sidebar {
transition: all 0.3s ease;
}
@media (max-width: 768px) {
.sidebar {
transform: translateX(-100%);
position: fixed;
z-index: 50;
height: 100vh;
top: 0;
left: 0;
}
.sidebar.open {
transform: translateX(0);
}
}
.video-player-container {
aspect-ratio: 16/9;
}
</style>
</head>
<body class="bg-gray-50 font-sans">
<div class="flex h-screen overflow-hidden">
<!-- Sidebar -->
<div class="sidebar bg-indigo-700 text-white w-64 flex-shrink-0 p-4 md:block">
<div class="flex items-center justify-between mb-8">
<h1 class="text-2xl font-bold">Trainer's Toolkit</h1>
<button id="closeSidebar" class="md:hidden text-white">
<i class="fas fa-times"></i>
</button>
</div>
<nav>
<div class="mb-6">
<h2 class="text-sm uppercase font-semibold text-indigo-200 mb-2">Navigation</h2>
<ul class="space-y-1">
<li>
<button id="dashboardBtn" class="w-full text-left px-3 py-2 rounded-md bg-indigo-800 flex items-center">
<i class="fas fa-home mr-2"></i> Dashboard
</button>
</li>
<li>
<button id="uploadBtn" class="w-full text-left px-3 py-2 rounded-md hover:bg-indigo-600 flex items-center">
<i class="fas fa-upload mr-2"></i> Upload Videos
</button>
</li>
<li>
<button id="libraryBtn" class="w-full text-left px-3 py-2 rounded-md hover:bg-indigo-600 flex items-center">
<i class="fas fa-video mr-2"></i> Video Library
</button>
</li>
<li>
<button id="tagsBtn" class="w-full text-left px-3 py-2 rounded-md hover:bg-indigo-600 flex items-center">
<i class="fas fa-tags mr-2"></i> Manage Tags
</button>
</li>
</ul>
</div>
<div class="mb-6">
<h2 class="text-sm uppercase font-semibold text-indigo-200 mb-2">Quick Filters</h2>
<ul class="space-y-1">
<li>
<button data-filter="recent" class="w-full text-left px-3 py-2 rounded-md hover:bg-indigo-600 flex items-center">
<i class="fas fa-clock mr-2"></i> Recent Videos
</button>
</li>
<li>
<button data-filter="week" class="w-full text-left px-3 py-2 rounded-md hover:bg-indigo-600 flex items-center">
<i class="fas fa-calendar-week mr-2"></i> This Week
</button>
</li>
<li>
<button data-filter="month" class="w-full text-left px-3 py-2 rounded-md hover:bg-indigo-600 flex items-center">
<i class="fas fa-calendar-alt mr-2"></i> This Month
</button>
</li>
</ul>
</div>
<div>
<h2 class="text-sm uppercase font-semibold text-indigo-200 mb-2">Popular Tags</h2>
<div class="tag-cloud" id="popularTags">
<!-- Tags will be dynamically inserted here -->
</div>
</div>
</nav>
</div>
<!-- Main Content -->
<div class="flex-1 flex flex-col overflow-hidden">
<!-- Top Navigation -->
<header class="bg-white shadow-sm z-10">
<div class="flex items-center justify-between px-6 py-4">
<div class="flex items-center">
<button id="menuBtn" class="mr-4 text-gray-600 md:hidden">
<i class="fas fa-bars"></i>
</button>
<h2 id="pageTitle" class="text-xl font-semibold text-gray-800">Dashboard</h2>
</div>
<div class="flex items-center space-x-4">
<div class="relative">
<input type="text" placeholder="Search videos..." class="pl-10 pr-4 py-2 border rounded-full text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500">
<i class="fas fa-search absolute left-3 top-3 text-gray-400"></i>
</div>
<div class="w-10 h-10 rounded-full bg-indigo-100 flex items-center justify-center text-indigo-700 font-semibold">
T
</div>
</div>
</div>
</header>
<!-- Page Content -->
<main class="flex-1 overflow-y-auto p-6 bg-gray-50" id="mainContent">
<!-- Dashboard Content -->
<div id="dashboardContent">
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
<div class="bg-white p-6 rounded-lg shadow">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500">Total Videos</p>
<h3 class="text-2xl font-bold" id="totalVideos">0</h3>
</div>
<div class="p-3 rounded-full bg-indigo-100 text-indigo-600">
<i class="fas fa-video"></i>
</div>
</div>
</div>
<div class="bg-white p-6 rounded-lg shadow">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500">Total Tags</p>
<h3 class="text-2xl font-bold" id="totalTags">0</h3>
</div>
<div class="p-3 rounded-full bg-green-100 text-green-600">
<i class="fas fa-tags"></i>
</div>
</div>
</div>
<div class="bg-white p-6 rounded-lg shadow">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500">Storage Used</p>
<h3 class="text-2xl font-bold" id="storageUsed">0 MB</h3>
</div>
<div class="p-3 rounded-full bg-blue-100 text-blue-600">
<i class="fas fa-database"></i>
</div>
</div>
</div>
</div>
<div class="bg-white p-6 rounded-lg shadow mb-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold">Recent Videos</h3>
<button id="viewAllVideos" class="text-indigo-600 hover:text-indigo-800 text-sm font-medium">View All</button>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4" id="recentVideos">
<!-- Recent videos will be inserted here -->
</div>
</div>
<div class="bg-white p-6 rounded-lg shadow">
<h3 class="text-lg font-semibold mb-4">Quick Actions</h3>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
<button id="quickUploadBtn" class="flex flex-col items-center justify-center p-4 border rounded-lg hover:bg-gray-50 transition-colors">
<div class="w-10 h-10 rounded-full bg-indigo-100 flex items-center justify-center text-indigo-600 mb-2">
<i class="fas fa-upload"></i>
</div>
<span class="text-sm font-medium">Upload Video</span>
</button>
<button id="quickTagBtn" class="flex flex-col items-center justify-center p-4 border rounded-lg hover:bg-gray-50 transition-colors">
<div class="w-10 h-10 rounded-full bg-green-100 flex items-center justify-center text-green-600 mb-2">
<i class="fas fa-tags"></i>
</div>
<span class="text-sm font-medium">Manage Tags</span>
</button>
<button id="quickShareBtn" class="flex flex-col items-center justify-center p-4 border rounded-lg hover:bg-gray-50 transition-colors">
<div class="w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center text-blue-600 mb-2">
<i class="fas fa-share-alt"></i>
</div>
<span class="text-sm font-medium">Share Videos</span>
</button>
<button id="quickStatsBtn" class="flex flex-col items-center justify-center p-4 border rounded-lg hover:bg-gray-50 transition-colors">
<div class="w-10 h-10 rounded-full bg-purple-100 flex items-center justify-center text-purple-600 mb-2">
<i class="fas fa-chart-line"></i>
</div>
<span class="text-sm font-medium">View Stats</span>
</button>
</div>
</div>
</div>
<!-- Upload Content -->
<div id="uploadContent" class="hidden">
<div class="bg-white p-6 rounded-lg shadow mb-6">
<h2 class="text-xl font-semibold mb-4">Upload Workout Videos</h2>
<div class="video-upload-area rounded-lg p-8 text-center mb-6" id="dropArea">
<i class="fas fa-cloud-upload-alt text-4xl text-indigo-500 mb-3"></i>
<h3 class="text-lg font-medium mb-1">Drag & Drop your workout videos here</h3>
<p class="text-gray-500 mb-4">or</p>
<label for="videoUpload" class="inline-flex items-center px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 cursor-pointer">
<i class="fas fa-folder-open mr-2"></i> Browse Files
<input type="file" id="videoUpload" class="hidden" accept="video/*" multiple>
</label>
</div>
<div class="mb-6">
<h3 class="text-lg font-medium mb-3">Upload Queue</h3>
<div class="border rounded-lg overflow-hidden">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">File Name</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Size</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Progress</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Action</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200" id="uploadQueue">
<!-- Upload queue will be inserted here -->
</tbody>
</table>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label for="workoutName" class="block text-sm font-medium text-gray-700 mb-1">Workout Name</label>
<input type="text" id="workoutName" class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500" placeholder="e.g. Upper Body Strength">
</div>
<div>
<label for="trainingName" class="block text-sm font-medium text-gray-700 mb-1">Training Name</label>
<input type="text" id="trainingName" class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500" placeholder="e.g. Shoulder Press Technique">
</div>
</div>
<div class="mt-6">
<label for="videoTags" class="block text-sm font-medium text-gray-700 mb-1">Tags (comma separated)</label>
<input type="text" id="videoTags" class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500" placeholder="e.g. strength, shoulders, technique">
</div>
<div class="mt-6">
<label for="videoDescription" class="block text-sm font-medium text-gray-700 mb-1">Description</label>
<textarea id="videoDescription" rows="3" class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500" placeholder="Add a description for this workout video..."></textarea>
</div>
<div class="mt-6 flex justify-end">
<button id="cancelUpload" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 mr-3">Cancel</button>
<button id="startUpload" class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">Start Upload</button>
</div>
</div>
</div>
<!-- Video Library Content -->
<div id="libraryContent" class="hidden">
<div class="bg-white p-6 rounded-lg shadow mb-6">
<div class="flex flex-col md:flex-row md:items-center md:justify-between mb-6">
<h2 class="text-xl font-semibold mb-4 md:mb-0">Video Library</h2>
<div class="flex flex-col sm:flex-row gap-3">
<div class="relative">
<select id="sortBy" class="appearance-none pl-3 pr-8 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 bg-white">
<option value="date-desc">Sort by: Newest First</option>
<option value="date-asc">Sort by: Oldest First</option>
<option value="name-asc">Sort by: Name (A-Z)</option>
<option value="name-desc">Sort by: Name (Z-A)</option>
</select>
<i class="fas fa-chevron-down absolute right-3 top-3 text-gray-400 pointer-events-none"></i>
</div>
<div class="relative">
<select id="filterBy" class="appearance-none pl-3 pr-8 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 bg-white">
<option value="all">Filter by: All Videos</option>
<option value="week">This Week</option>
<option value="month">This Month</option>
<option value="year">This Year</option>
</select>
<i class="fas fa-chevron-down absolute right-3 top-3 text-gray-400 pointer-events-none"></i>
</div>
</div>
</div>
<div class="mb-4">
<div class="tag-cloud" id="activeFilters">
<!-- Active filters will be shown here -->
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6" id="videoGrid">
<!-- Videos will be inserted here -->
</div>
<div class="mt-6 flex justify-center" id="loadMoreContainer">
<button id="loadMoreVideos" class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">Load More Videos</button>
</div>
</div>
</div>
<!-- Tags Management Content -->
<div id="tagsContent" class="hidden">
<div class="bg-white p-6 rounded-lg shadow mb-6">
<div class="flex justify-between items-center mb-6">
<h2 class="text-xl font-semibold">Manage Tags</h2>
<button id="addNewTag" class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 flex items-center">
<i class="fas fa-plus mr-2"></i> New Tag
</button>
</div>
<div class="mb-6">
<div class="relative">
<input type="text" id="tagSearch" class="w-full pl-10 pr-4 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500" placeholder="Search tags...">
<i class="fas fa-search absolute left-3 top-3 text-gray-400"></i>
</div>
</div>
<div class="border rounded-lg overflow-hidden">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Tag Name</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Color</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Videos Count</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200" id="tagsTable">
<!-- Tags will be inserted here -->
</tbody>
</table>
</div>
</div>
</div>
<!-- Video Detail Modal -->
<div id="videoModal" class="fixed inset-0 z-50 hidden overflow-y-auto">
<div class="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
</div>
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">&#8203;</span>
<div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-4xl sm:w-full">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="flex justify-between items-start">
<h3 class="text-lg font-medium text-gray-900" id="modalVideoTitle">Video Title</h3>
<button id="closeModal" class="text-gray-400 hover:text-gray-500">
<i class="fas fa-times"></i>
</button>
</div>
<div class="mt-4 video-player-container bg-black rounded-lg overflow-hidden">
<video controls class="w-full h-full" id="modalVideoPlayer">
Your browser does not support the video tag.
</video>
</div>
<div class="mt-4 grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="md:col-span-2">
<div class="flex items-center mb-2">
<i class="fas fa-calendar-alt text-gray-500 mr-2"></i>
<span class="text-sm text-gray-600" id="modalVideoDate">Uploaded on: </span>
</div>
<div class="flex items-center mb-2">
<i class="fas fa-tag text-gray-500 mr-2"></i>
<div class="tag-cloud" id="modalVideoTags">
<!-- Tags will be inserted here -->
</div>
</div>
<div class="flex items-center mb-2">
<i class="fas fa-dumbbell text-gray-500 mr-2"></i>
<span class="text-sm text-gray-600" id="modalVideoWorkout">Workout: </span>
</div>
<div class="flex items-center mb-2">
<i class="fas fa-running text-gray-500 mr-2"></i>
<span class="text-sm text-gray-600" id="modalVideoTraining">Training: </span>
</div>
<div class="mt-3">
<p class="text-sm text-gray-700" id="modalVideoDescription">No description provided.</p>
</div>
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<h4 class="font-medium mb-3">Share Options</h4>
<div class="space-y-3">
<button class="w-full flex items-center justify-center px-4 py-2 border border-gray-300 rounded-md bg-white text-gray-700 hover:bg-gray-50">
<i class="fab fa-whatsapp text-green-500 mr-2"></i> Share via WhatsApp
</button>
<button class="w-full flex items-center justify-center px-4 py-2 border border-gray-300 rounded-md bg-white text-gray-700 hover:bg-gray-50">
<i class="fas fa-envelope text-blue-500 mr-2"></i> Share via Email
</button>
<div class="mt-2">
<label class="block text-sm font-medium text-gray-700 mb-1">Direct Link</label>
<div class="flex">
<input type="text" id="videoLink" class="flex-1 px-3 py-2 border rounded-l-md focus:outline-none focus:ring-2 focus:ring-indigo-500" readonly>
<button id="copyLink" class="px-3 py-2 bg-indigo-600 text-white rounded-r-md hover:bg-indigo-700">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
</div>
<div class="mt-4 pt-4 border-t border-gray-200">
<h4 class="font-medium mb-3">Video Actions</h4>
<div class="space-y-2">
<button id="editVideoBtn" class="w-full flex items-center justify-start px-4 py-2 text-gray-700 hover:bg-gray-100 rounded-md">
<i class="fas fa-edit text-indigo-500 mr-2"></i> Edit Details
</button>
<button id="downloadVideoBtn" class="w-full flex items-center justify-start px-4 py-2 text-gray-700 hover:bg-gray-100 rounded-md">
<i class="fas fa-download text-green-500 mr-2"></i> Download Video
</button>
<button id="deleteVideoBtn" class="w-full flex items-center justify-start px-4 py-2 text-gray-700 hover:bg-gray-100 rounded-md">
<i class="fas fa-trash-alt text-red-500 mr-2"></i> Delete Video
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Edit Video Modal -->
<div id="editVideoModal" class="fixed inset-0 z-50 hidden overflow-y-auto">
<div class="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
</div>
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">&#8203;</span>
<div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-2xl sm:w-full">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="flex justify-between items-start">
<h3 class="text-lg font-medium text-gray-900">Edit Video Details</h3>
<button id="closeEditModal" class="text-gray-400 hover:text-gray-500">
<i class="fas fa-times"></i>
</button>
</div>
<div class="mt-4">
<input type="hidden" id="editVideoId">
<div class="mb-4">
<label for="editVideoTitle" class="block text-sm font-medium text-gray-700 mb-1">Video Title</label>
<input type="text" id="editVideoTitle" class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500">
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div>
<label for="editWorkoutName" class="block text-sm font-medium text-gray-700 mb-1">Workout Name</label>
<input type="text" id="editWorkoutName" class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500">
</div>
<div>
<label for="editTrainingName" class="block text-sm font-medium text-gray-700 mb-1">Training Name</label>
<input type="text" id="editTrainingName" class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500">
</div>
</div>
<div class="mb-4">
<label for="editVideoTags" class="block text-sm font-medium text-gray-700 mb-1">Tags (comma separated)</label>
<input type="text" id="editVideoTags" class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500">
</div>
<div>
<label for="editVideoDescription" class="block text-sm font-medium text-gray-700 mb-1">Description</label>
<textarea id="editVideoDescription" rows="3" class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500"></textarea>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button id="saveVideoChanges" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-indigo-600 text-base font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:ml-3 sm:w-auto sm:text-sm">
Save Changes
</button>
<button id="cancelVideoEdit" class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
Cancel
</button>
</div>
</div>
</div>
</div>
<!-- Add Tag Modal -->
<div id="addTagModal" class="fixed inset-0 z-50 hidden overflow-y-auto">
<div class="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
</div>
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">&#8203;</span>
<div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-md sm:w-full">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="flex justify-between items-start">
<h3 class="text-lg font-medium text-gray-900">Add New Tag</h3>
<button id="closeTagModal" class="text-gray-400 hover:text-gray-500">
<i class="fas fa-times"></i>
</button>
</div>
<div class="mt-4">
<div class="mb-4">
<label for="newTagName" class="block text-sm font-medium text-gray-700 mb-1">Tag Name</label>
<input type="text" id="newTagName" class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500">
</div>
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-1">Tag Color</label>
<div class="flex flex-wrap gap-2">
<div class="w-8 h-8 rounded-full bg-red-500 cursor-pointer border-2 border-transparent hover:border-gray-300" data-color="red"></div>
<div class="w-8 h-8 rounded-full bg-blue-500 cursor-pointer border-2 border-transparent hover:border-gray-300" data-color="blue"></div>
<div class="w-8 h-8 rounded-full bg-green-500 cursor-pointer border-2 border-transparent hover:border-gray-300" data-color="green"></div>
<div class="w-8 h-8 rounded-full bg-yellow-500 cursor-pointer border-2 border-transparent hover:border-gray-300" data-color="yellow"></div>
<div class="w-8 h-8 rounded-full bg-indigo-500 cursor-pointer border-2 border-transparent hover:border-gray-300" data-color="indigo"></div>
<div class="w-8 h-8 rounded-full bg-purple-500 cursor-pointer border-2 border-transparent hover:border-gray-300" data-color="purple"></div>
<div class="w-8 h-8 rounded-full bg-pink-500 cursor-pointer border-2 border-transparent hover:border-gray-300" data-color="pink"></div>
<div class="w-8 h-8 rounded-full bg-gray-500 cursor-pointer border-2 border-transparent hover:border-gray-300" data-color="gray"></div>
</div>
<input type="hidden" id="selectedTagColor" value="indigo">
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button id="saveNewTag" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-indigo-600 text-base font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:ml-3 sm:w-auto sm:text-sm">
Add Tag
</button>
<button id="cancelNewTag" class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
Cancel
</button>
</div>
</div>
</div>
</div>
<!-- Confirmation Modal -->
<div id="confirmModal" class="fixed inset-0 z-50 hidden overflow-y-auto">
<div class="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
</div>
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">&#8203;</span>
<div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<i class="fas fa-exclamation-triangle text-red-600"></i>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900" id="confirmModalTitle">Confirm Action</h3>
<div class="mt-2">
<p class="text-sm text-gray-500" id="confirmModalMessage">Are you sure you want to perform this action?</p>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button id="confirmAction" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">
Confirm
</button>
<button id="cancelAction" class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
Cancel
</button>
</div>
</div>
</div>
</div>
<!-- Notification Toast -->
<div id="toast" class="fixed bottom-4 right-4 hidden">
<div class="bg-green-500 text-white px-4 py-2 rounded-md shadow-lg flex items-center">
<i class="fas fa-check-circle mr-2"></i>
<span id="toastMessage">Operation completed successfully!</span>
<button id="closeToast" class="ml-4">
<i class="fas fa-times"></i>
</button>
</div>
</div>
</main>
</div>
</div>
<script>
// Sample data storage (in a real app, this would be a database)
let videos = [];
let tags = [
{ id: 1, name: 'strength', color: 'red', count: 0 },
{ id: 2, name: 'cardio', color: 'blue', count: 0 },
{ id: 3, name: 'flexibility', color: 'green', count: 0 },
{ id: 4, name: 'technique', color: 'yellow', count: 0 },
{ id: 5, name: 'beginner', color: 'indigo', count: 0 },
{ id: 6, name: 'advanced', color: 'purple', count: 0 }
];
// DOM Elements
const menuBtn = document.getElementById('menuBtn');
const closeSidebar = document.getElementById('closeSidebar');
const sidebar = document.querySelector('.sidebar');
const pageTitle = document.getElementById('pageTitle');
const mainContent = document.getElementById('mainContent');
// Content sections
const dashboardContent = document.getElementById('dashboardContent');
const uploadContent = document.getElementById('uploadContent');
const libraryContent = document.getElementById('libraryContent');
const tagsContent = document.getElementById('tagsContent');
// Navigation buttons
const dashboardBtn = document.getElementById('dashboardBtn');
const uploadBtn = document.getElementById('uploadBtn');
const libraryBtn = document.getElementById('libraryBtn');
const tagsBtn = document.getElementById('tagsBtn');
// Quick action buttons
const quickUploadBtn = document.getElementById('quickUploadBtn');
const quickTagBtn = document.getElementById('quickTagBtn');
const quickShareBtn = document.getElementById('quickShareBtn');
const quickStatsBtn = document.getElementById('quickStatsBtn');
const viewAllVideos = document.getElementById('viewAllVideos');
// Upload section elements
const dropArea = document.getElementById('dropArea');
const videoUpload = document.getElementById('videoUpload');
const uploadQueue = document.getElementById('uploadQueue');
const startUpload = document.getElementById('startUpload');
const cancelUpload = document.getElementById('cancelUpload');
// Library section elements
const videoGrid = document.getElementById('videoGrid');
const sortBy = document.getElementById('sortBy');
const filterBy = document.getElementById('filterBy');
const activeFilters = document.getElementById('activeFilters');
const loadMoreVideos = document.getElementById('loadMoreVideos');
const loadMoreContainer = document.getElementById('loadMoreContainer');
const popularTags = document.getElementById('popularTags');
// Tags section elements
const tagsTable = document.getElementById('tagsTable');
const addNewTag = document.getElementById('addNewTag');
const tagSearch = document.getElementById('tagSearch');
// Modal elements
const videoModal = document.getElementById('videoModal');
const modalVideoTitle = document.getElementById('modalVideoTitle');
const modalVideoPlayer = document.getElementById('modalVideoPlayer');
const modalVideoDate = document.getElementById('modalVideoDate');
const modalVideoTags = document.getElementById('modalVideoTags');
const modalVideoWorkout = document.getElementById('modalVideoWorkout');
const modalVideoTraining = document.getElementById('modalVideoTraining');
const modalVideoDescription = document.getElementById('modalVideoDescription');
const videoLink = document.getElementById('videoLink');
const copyLink = document.getElementById('copyLink');
const closeModal = document.getElementById('closeModal');
const editVideoModal = document.getElementById('editVideoModal');
const editVideoId = document.getElementById('editVideoId');
const editVideoTitle = document.getElementById('editVideoTitle');
const editWorkoutName = document.getElementById('editWorkoutName');
const editTrainingName = document.getElementById('editTrainingName');
const editVideoTags = document.getElementById('editVideoTags');
const editVideoDescription = document.getElementById('editVideoDescription');
const saveVideoChanges = document.getElementById('saveVideoChanges');
const cancelVideoEdit = document.getElementById('cancelVideoEdit');
const closeEditModal = document.getElementById('closeEditModal');
const addTagModal = document.getElementById('addTagModal');
const newTagName = document.getElementById('newTagName');
const selectedTagColor = document.getElementById('selectedTagColor');
const saveNewTag = document.getElementById('saveNewTag');
const cancelNewTag = document.getElementById('cancelNewTag');
const closeTagModal = document.getElementById('closeTagModal');
const confirmModal = document.getElementById('confirmModal');
const confirmModalTitle = document.getElementById('confirmModalTitle');
const confirmModalMessage = document.getElementById('confirmModalMessage');
const confirmAction = document.getElementById('confirmAction');
const cancelAction = document.getElementById('cancelAction');
const toast = document.getElementById('toast');
const toastMessage = document.getElementById('toastMessage');
const closeToast = document.getElementById('closeToast');
// App state
let currentView = 'dashboard';
let currentVideoPage = 1;
let videosPerPage = 8;
let selectedVideoId = null;
let filesToUpload = [];
let activeTagFilters = [];
let actionToConfirm = null;
// Initialize the app
function init() {
// Load sample data
loadSampleVideos();
updateTagCounts();
// Set up event listeners
setupEventListeners();
// Render initial views
renderDashboard();
renderPopularTags();
}
// Load sample videos for demo purposes
function loadSampleVideos() {
const sampleVideos = [
{
id: 1,
title: 'Shoulder Press Technique',
workout: 'Upper Body Strength',
training: 'Shoulder Press',
description: 'Detailed explanation of proper shoulder press technique to maximize results and prevent injury.',
tags: ['strength', 'shoulders', 'technique'],
date: new Date('2023-06-15'),
size: 45,
thumbnail: 'https://img.freepik.com/free-photo/fit-man-training-gym_651396-1025.jpg',
videoUrl: 'https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_1mb.mp4'
},
{
id: 2,
title: 'Squat Depth and Form',
workout: 'Lower Body Strength',
training: 'Barbell Squat',
description: 'How to achieve proper squat depth while maintaining good form throughout the movement.',
tags: ['strength', 'legs', 'technique', 'beginner'],
date: new Date('2023-06-10'),
size: 62,
thumbnail: 'https://img.freepik.com/free-photo/young-healthy-man-athlete-doing-exercise-with-ropes-gym-single-male-model-performing-hard-training-his-upper-body-concept-healthy-lifestyle-sport-fitness-bodybuilding-wellbeing_155003-27804.jpg',
videoUrl: 'https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_1mb.mp4'
},
{
id: 3,
title: 'HIIT Circuit Demo',
workout: 'Cardio Blast',
training: 'HIIT Routine',
description: 'Full demonstration of our 20-minute high intensity interval training circuit.',
tags: ['cardio', 'hiit', 'full-body'],
date: new Date('2023-06-05'),
size: 85,
thumbnail: 'https://img.freepik.com/free-photo/young-fitness-man-studio_7502-5008.jpg',
videoUrl: 'https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_1mb.mp4'
},
{
id: 4,
title: 'Deadlift Variations',
workout: 'Power Lifting',
training: 'Deadlift',
description: 'Comparison of conventional, sumo, and Romanian deadlift variations with pros and cons of each.',
tags: ['strength', 'deadlift', 'advanced'],
date: new Date('2023-05-28'),
size: 78,
thumbnail: 'https://img.freepik.com/free-photo/young-fitness-man-studio_7502-5006.jpg',
videoUrl: 'https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_1mb.mp4'
},
{
id: 5,
title: 'Dynamic Warmup Routine',
workout: 'Pre-Workout',
training: 'Warmup',
description: '10-minute dynamic warmup routine to prepare your body for intense training sessions.',
tags: ['warmup', 'flexibility', 'beginner'],
date: new Date('2023-05-20'),
size: 35,
thumbnail: 'https://img.freepik.com/free-photo/young-sports-man-training-gym_1303-14701.jpg',
videoUrl: 'https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_1mb.mp4'
},
{
id: 6,
title: 'Advanced Pushup Variations',
workout: 'Bodyweight Training',
training: 'Pushups',
description: '10 challenging pushup variations to take your upper body training to the next level.',
tags: ['strength', 'bodyweight', 'advanced'],
date: new Date('2023-05-15'),
size: 52,
thumbnail: 'https://img.freepik.com/free-photo/young-sports-man-training-gym_1303-14696.jpg',
videoUrl: 'https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_1mb.mp4'
},
{
id: 7,
title: 'Foam Rolling Techniques',
workout: 'Recovery',
training: 'Mobility',
description: 'How to properly use a foam roller to improve recovery and mobility after workouts.',
tags: ['recovery', 'flexibility', 'beginner'],
date: new Date('2023-05-10'),
size: 48,
thumbnail: 'https://img.freepik.com/free-photo/young-sports-man-training-gym_1303-14700.jpg',
videoUrl: 'https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_1mb.mp4'
},
{
id: 8,
title: 'Kettlebell Swing Form',
workout: 'Functional Training',
training: 'Kettlebell',
description: 'Step-by-step guide to mastering the kettlebell swing for power and endurance.',
tags: ['strength', 'kettlebell', 'technique'],
date: new Date('2023-05-05'),
size: 55,
thumbnail: 'https://img.freepik.com/free-photo/young-sports-man-training-gym_1303-14699.jpg',
videoUrl: 'https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_1mb.mp4'
}
];
videos = sampleVideos;
}
// Update tag counts based on videos
function updateTagCounts() {
tags.forEach(tag => {
tag.count = videos.filter(video => video.tags.includes(tag.name)).length;
});
}
// Set up all event listeners
function setupEventListeners() {
// Navigation
menuBtn.addEventListener('click', toggleSidebar);
closeSidebar.addEventListener('click', toggleSidebar);
dashboardBtn.addEventListener('click', () => switchView('dashboard'));
uploadBtn.addEventListener('click', () => switchView('upload'));
libraryBtn.addEventListener('click', () => switchView('library'));
tagsBtn.addEventListener('click', () => switchView('tags'));
// Quick actions
quickUploadBtn.addEventListener('click', () => switchView('upload'));
quickTagBtn.addEventListener('click', () => switchView('tags'));
quickShareBtn.addEventListener('click', () => showToast('Share feature coming soon!'));
quickStatsBtn.addEventListener('click', () => showToast('Statistics feature coming soon!'));
viewAllVideos.addEventListener('click', () => switchView('library'));
// Upload section
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, preventDefaults, false);
});
['dragenter', 'dragover'].forEach(eventName => {
dropArea.addEventListener(eventName, highlight, false);
});
['dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, unhighlight, false);
});
dropArea.addEventListener('drop', handleDrop, false);
videoUpload.addEventListener('change', handleFiles, false);
startUpload.addEventListener('click', uploadFiles);
cancelUpload.addEventListener('click', clearUploadQueue);
// Library section
sortBy.addEventListener('change', renderVideoLibrary);
filterBy.addEventListener('change', renderVideoLibrary);
loadMoreVideos.addEventListener('click', loadMoreVideosHandler);
// Tags section
addNewTag.addEventListener('click', showAddTagModal);
tagSearch.addEventListener('input', filterTags);
// Video modal
closeModal.addEventListener('click', () => videoModal.classList.add('hidden'));
copyLink.addEventListener('click', copyVideoLink);
editVideoBtn.addEventListener('click', showEditVideoModal);
downloadVideoBtn.addEventListener('click', downloadVideo);
deleteVideoBtn.addEventListener('click', confirmDeleteVideo);
// Edit video modal
closeEditModal.addEventListener('click', () => editVideoModal.classList.add('hidden'));
cancelVideoEdit.addEventListener('click', () => editVideoModal.classList.add('hidden'));
saveVideoChanges.addEventListener('click', saveVideoEdits);
// Add tag modal
closeTagModal.addEventListener('click', () => addTagModal.classList.add('hidden'));
cancelNewTag.addEventListener('click', () => addTagModal.classList.add('hidden'));
saveNewTag.addEventListener('click', addNewTagHandler);
// Color selection in add tag modal
document.querySelectorAll('[data-color]').forEach(el => {
el.addEventListener('click', function() {
document.querySelectorAll('[data-color]').forEach(item => {
item.classList.remove('border-gray-300');
item.classList.add('border-transparent');
});
this.classList.remove('border-transparent');
this.classList.add('border-gray-300');
selectedTagColor.value = this.getAttribute('data-color');
});
});
// Confirmation modal
closeEditModal.addEventListener('click', () => confirmModal.classList.add('hidden'));
cancelAction.addEventListener('click', () => confirmModal.classList.add('hidden'));
confirmAction.addEventListener('click', performConfirmedAction);
// Toast notification
closeToast.addEventListener('click', () => toast.classList.add('hidden'));
// Filter buttons
document.querySelectorAll('[data-filter]').forEach(btn => {
btn.addEventListener('click', function() {
const filter = this.getAttribute('data-filter');
applyQuickFilter(filter);
});
});
}
// Toggle sidebar on mobile
function toggleSidebar() {
sidebar.classList.toggle('open');
}
// Switch between different views
function switchView(view) {
currentView = view;
// Hide all content sections
dashboardContent.classList.add('hidden');
uploadContent.classList.add('hidden');
libraryContent.classList.add('hidden');
tagsContent.classList.add('hidden');
// Reset active states on nav buttons
dashboardBtn.classList.remove('bg-indigo-800');
uploadBtn.classList.remove('bg-indigo-800');
libraryBtn.classList.remove('bg-indigo-800');
tagsBtn.classList.remove('bg-indigo-800');
// Show the selected content section
switch(view) {
case 'dashboard':
dashboardContent.classList.remove('hidden');
dashboardBtn.classList.add('bg-indigo-800');
pageTitle.textContent = 'Dashboard';
renderDashboard();
break;
case 'upload':
uploadContent.classList.remove('hidden');
uploadBtn.classList.add('bg-indigo-800');
pageTitle.textContent = 'Upload Videos';
break;
case 'library':
libraryContent.classList.remove('hidden');
libraryBtn.classList.add('bg-indigo-800');
pageTitle.textContent = 'Video Library';
currentVideoPage = 1;
renderVideoLibrary();
break;
case 'tags':
tagsContent.classList.remove('hidden');
tagsBtn.classList.add('bg-indigo-800');
pageTitle.textContent = 'Manage Tags';
renderTagsTable();
break;
}
// Close sidebar on mobile after selection
if (window.innerWidth < 768) {
sidebar.classList.remove('open');
}
}
// Render dashboard content
function renderDashboard() {
// Update stats
document.getElementById('totalVideos').textContent = videos.length;
document.getElementById('totalTags').textContent = tags.length;
const totalSize = videos.reduce((sum, video) => sum + video.size, 0);
document.getElementById('storageUsed').textContent = `${totalSize} MB`;
// Render recent videos
const recentVideosContainer = document.getElementById('recentVideos');
recentVideosContainer.innerHTML = '';
// Sort by date (newest first) and take first 3
const recentVideos = [...videos].sort((a, b) => b.date - a.date).slice(0, 3);
recentVideos.forEach(video => {
const videoElement = createVideoCard(video, true);
recentVideosContainer.appendChild(videoElement);
});
}
// Render video library
function renderVideoLibrary() {
videoGrid.innerHTML = '';
currentVideoPage = 1;
let filteredVideos = [...videos];
// Apply tag filters if any
if (activeTagFilters.length > 0) {
filteredVideos = filteredVideos.filter(video =>
activeTagFilters.some(tag => video.tags.includes(tag))
);
}
// Apply time filter
const timeFilter = filterBy.value;
if (timeFilter !== 'all') {
const now = new Date();
let startDate;
switch(timeFilter) {
case 'week':
startDate = new Date(now.setDate(now.getDate() - 7));
break;
case 'month':
startDate = new Date(now.setMonth(now.getMonth() - 1));
break;
case 'year':
startDate = new Date(now.setFullYear(now.getFullYear() - 1));
break;
}
filteredVideos = filteredVideos.filter(video => video.date >= startDate);
}
// Apply sorting
const sortOption = sortBy.value;
switch(sortOption) {
case 'date-desc':
filteredVideos.sort((a, b) => b.date - a.date);
break;
case 'date-asc':
filteredVideos.sort((a, b) => a.date - b.date);
break;
case 'name-asc':
filteredVideos.sort((a, b) => a.title.localeCompare(b.title));
break;
case 'name-desc':
filteredVideos.sort((a, b) => b.title.localeCompare(a.title));
break;
}
// Display active filters
renderActiveFilters();
// Check if there are videos to display
if (filteredVideos.length === 0) {
videoGrid.innerHTML = `
<div class="col-span-full text-center py-10">
<i class="fas fa-video-slash text-4xl text-gray-400 mb-3"></i>
<h3 class="text-lg font-medium text-gray-700">No videos found</h3>
<p class="text-gray-500">Try adjusting your filters or upload new videos</p>
</div>
`;
loadMoreContainer.classList.add('hidden');
return;
}
// Display first page of videos
displayVideosPaginated(filteredVideos);
// Show/hide load more button
if (filteredVideos.length > videosPerPage * currentVideoPage) {
loadMoreContainer.classList.remove('hidden');
} else {
loadMoreContainer.classList.add('hidden');
}
}
// Display videos with pagination
function displayVideosPaginated(videoList) {
const startIndex = 0;
const endIndex = Math.min(videoList.length, videosPerPage * currentVideoPage);
for (let i = startIndex; i < endIndex; i++) {
const video = videoList[i];
const videoElement = createVideoCard(video);
videoGrid.appendChild(videoElement);
}
}
// Load more videos handler
function loadMoreVideosHandler() {
currentVideoPage++;
renderVideoLibrary();
// Scroll to the bottom of the video grid
setTimeout(() => {
videoGrid.scrollIntoView({ behavior: 'smooth', block: 'end' });
}, 100);
}
// Create a video card element
function createVideoCard(video, isSmall = false) {
const videoElement = document.createElement('div');
videoElement.className = 'bg-white rounded-lg overflow-hidden shadow-md video-thumbnail';
videoElement.dataset.id = video.id;
// Format date
const formattedDate = video.date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
});
if (isSmall) {
videoElement.innerHTML = `
<div class="cursor-pointer" onclick="openVideoModal(${video.id})">
<div class="relative pb-[56.25%] bg-gray-200">
<img src="${video.thumbnail}" alt="${video.title}" class="absolute h-full w-full object-cover">
<div class="absolute bottom-2 right-2 bg-black bg-opacity-70 text-white text-xs px-2 py-1 rounded">
${video.size} MB
</div>
</div>
<div class="p-3">
<h3 class="font-medium text-gray-800 truncate">${video.title}</h3>
<p class="text-xs text-gray-500 mt-1">${formattedDate}</p>
<div class="flex flex-wrap gap-1 mt-2">
${video.tags.map(tag => createTagElement(tag)).join('')}
</div>
</div>
</div>
`;
} else {
videoElement.innerHTML = `
<div class="cursor-pointer" onclick="openVideoModal(${video.id})">
<div class="relative pb-[56.25%] bg-gray-200">
<img src="${video.thumbnail}" alt="${video.title}" class="absolute h-full w-full object-cover">
<div class="absolute inset-0 flex items-center justify-center opacity-0 hover:opacity-100 bg-black bg-opacity-30 transition-opacity">
<div class="bg-white bg-opacity-80 rounded-full p-3">
<i class="fas fa-play text-indigo-600"></i>
</div>
</div>
<div class="absolute bottom-2 right-2 bg-black bg-opacity-70 text-white text-xs px-2 py-1 rounded">
${video.size} MB
</div>
</div>
<div class="p-4">
<h3 class="font-medium text-gray-800 truncate">${video.title}</h3>
<p class="text-sm text-gray-500 mt-1">${video.workout}${video.training}</p>
<p class="text-xs text-gray-500 mt-1">${formattedDate}</p>
<div class="flex flex-wrap gap-1 mt-2">
${video.tags.map(tag => createTagElement(tag)).join('')}
</div>
</div>
</div>
`;
}
return videoElement;
}
// Create a tag element
function createTagElement(tagName, isClickable = true) {
const tag = tags.find(t => t.name === tagName);
const color = tag ? tag.color : 'gray';
if (isClickable) {
return `<span class="tag-item inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-${color}-100 text-${color}-800" onclick="event.stopPropagation(); filterByTag('${tagName}')">${tagName}</span>`;
} else {
return `<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-${color}-100 text-${color}-800">${tagName}</span>`;
}
}
// Render active filters
function renderActiveFilters() {
activeFilters.innerHTML = '';
if (activeTagFilters.length > 0) {
const filterTitle = document.createElement('span');
filterTitle.className = 'text-sm text-gray-500 mr-2';
filterTitle.textContent = 'Active filters:';
activeFilters.appendChild(filterTitle);
activeTagFilters.forEach(tag => {
const tagElement = document.createElement('span');
tagElement.className = 'tag-item inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-indigo-100 text-indigo-800';
tagElement.innerHTML = `${tag} <button class="ml-1 text-indigo-500 hover:text-indigo-700" onclick="removeTagFilter('${tag}')"><i class="fas fa-times"></i></button>`;
activeFilters.appendChild(tagElement);
});
if (activeTagFilters.length > 0) {
const clearAll = document.createElement('button');
clearAll.className = 'text-sm text-indigo-600 hover:text-indigo-800 ml-2';
clearAll.textContent = 'Clear all';
clearAll.onclick = clearAllFilters;
activeFilters.appendChild(clearAll);
}
}
}
// Filter by tag
function filterByTag(tagName) {
if (!activeTagFilters.includes(tagName)) {
activeTagFilters.push(tagName);
renderVideoLibrary();
}
}
// Remove tag filter
function removeTagFilter(tagName) {
activeTagFilters = activeTagFilters.filter(tag => tag !== tagName);
renderVideoLibrary();
}
// Clear all filters
function clearAllFilters() {
activeTagFilters = [];
filterBy.value = 'all';
renderVideoLibrary();
}
// Apply quick filter from sidebar
function applyQuickFilter(filter) {
activeTagFilters = [];
switch(filter) {
case 'recent':
sortBy.value = 'date-desc';
filterBy.value = 'all';
break;
case 'week':
filterBy.value = 'week';
break;
case 'month':
filterBy.value = 'month';
break;
}
switchView('library');
}
// Render popular tags in sidebar
function renderPopularTags() {
popularTags.innerHTML = '';
// Sort tags by count (descending) and take top 6
const popular = [...tags].sort((a, b) => b.count - a.count).slice(0, 6);
popular.forEach(tag => {
const tagElement = document.createElement('span');
tagElement.className = `tag-item inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-${tag.color}-100 text-${tag.color}-800`;
tagElement.textContent = tag.name;
tagElement.onclick = function() {
switchView('library');
filterByTag(tag.name);
};
popularTags.appendChild(tagElement);
});
}
// Render tags table
function renderTagsTable() {
tagsTable.innerHTML = '';
if (tags.length === 0) {
tagsTable.innerHTML = `
<tr>
<td colspan="4" class="px-6 py-4 text-center text-gray-500">
No tags found. Create your first tag to get started.
</td>
</tr>
`;
return;
}
tags.forEach(tag => {
const row = document.createElement('tr');
row.innerHTML = `
<td class="px-6 py-4 whitespace-nowrap">
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-${tag.color}-100 text-${tag.color}-800">
${tag.name}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="w-4 h-4 rounded-full bg-${tag.color}-500"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
${tag.count} videos
</td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button class="text-indigo-600 hover:text-indigo-900 mr-3" onclick="editTag(${tag.id})">
<i class="fas fa-edit"></i>
</button>
<button class="text-red-600 hover:text-red-900" onclick="confirmDeleteTag(${tag.id})">
<i class="fas fa-trash-alt"></i>
</button>
</td>
`;
tagsTable.appendChild(row);
});
}
// Filter tags based on search input
function filterTags() {
const searchTerm = tagSearch.value.toLowerCase();
document.querySelectorAll('#tagsTable tr').forEach(row => {
if (row.cells.length === 1) return; // Skip the "no tags" row
const tagName = row.cells[0].textContent.toLowerCase();
if (tagName.includes(searchTerm)) {
row.style.display = '';
} else {
row.style.display = 'none';
}
});
}
// Show add tag modal
function showAddTagModal() {
newTagName.value = '';
selectedTagColor.value = 'indigo';
// Reset color selection
document.querySelectorAll('[data-color]').forEach(item => {
item.classList.remove('border-gray-300');
item.classList.add('border-transparent');
});
// Select indigo by default
document.querySelector('[data-color="indigo"]').classList.add('border-gray-300');
addTagModal.classList.remove('hidden');
}
// Add new tag handler
function addNewTagHandler() {
const name = newTagName.value.trim();
const color = selectedTagColor.value;
if (!name) {
showToast('Please enter a tag name');
return;
}
if (tags.some(tag => tag.name === name)) {
showToast('Tag already exists');
return;
}
const newTag = {
id: tags.length > 0 ? Math.max(...tags.map(t => t.id)) + 1 : 1,
name,
color,
count: 0
};
tags.push(newTag);
renderTagsTable();
renderPopularTags();
addTagModal.classList.add('hidden');
showToast('Tag added successfully');
}
// Edit tag
function editTag(tagId) {
// In a real app, this would open an edit modal
showToast('Edit tag feature coming soon!');
}
// Confirm tag deletion
function confirmDeleteTag(tagId) {
const tag = tags.find(t => t.id === tagId);
if (!tag) return;
if (tag.count > 0) {
showToast('Cannot delete tag that is in use by videos');
return;
}
confirmModalTitle.textContent = 'Delete Tag';
confirmModalMessage.textContent = `Are you sure you want to delete the tag "${tag.name}"? This action cannot be undone.`;
actionToConfirm = () => {
tags = tags.filter(t => t.id !== tagId);
renderTagsTable();
renderPopularTags();
confirmModal.classList.add('hidden');
showToast('Tag deleted successfully');
};
confirmModal.classList.remove('hidden');
}
// Perform confirmed action
function performConfirmedAction() {
if (actionToConfirm) {
actionToConfirm();
actionToConfirm = null;
}
confirmModal.classList.add('hidden');
}
// Upload section functions
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
function highlight() {
dropArea.classList.add('dragover');
}
function unhighlight() {
dropArea.classList.remove('dragover');
}
function handleDrop(e) {
const dt = e.dataTransfer;
const files = dt.files;
handleFiles({ target: { files } });
}
function handleFiles(e) {
filesToUpload = [...e.target.files];
renderUploadQueue();
}
function renderUploadQueue() {
uploadQueue.innerHTML = '';
if (filesToUpload.length === 0) {
uploadQueue.innerHTML = `
<tr>
<td colspan="4" class="px-6 py-4 text-center text-gray-
</html>