feat: migrate player.html to wd-* design system

This commit is contained in:
Zac Gaetano 2026-05-21 23:15:18 -04:00
parent 9ff80f8cc1
commit 6176791174

View file

@ -1,496 +1,546 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Wild Dragon - Asset Player</title> <link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="/css/common.css"> <title>Player — Dragonflight</title>
<style> <link rel="stylesheet" href="/dist/app.css">
.player-container { <style>
display: grid; .player-container {
grid-template-columns: 1fr 300px; display: grid;
gap: var(--spacing-lg); grid-template-columns: 1fr 300px;
height: calc(100vh - 110px); gap: 16px;
padding: var(--spacing-lg); height: calc(100vh - 110px);
overflow: hidden; overflow: hidden;
} }
.player-main { .player-main {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--spacing-lg); gap: 16px;
} }
.video-container { .video-container {
flex: 1; flex: 1;
background-color: var(--color-bg-tertiary); background-color: var(--bg-surface);
border: 1px solid var(--color-border); border: 1px solid var(--border);
border-radius: var(--radius-lg); border-radius: 8px;
overflow: hidden; overflow: hidden;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.video-player { .video-player {
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: var(--color-bg-primary); background-color: var(--bg-base);
} }
.metadata-panel { .metadata-panel {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--spacing-md); gap: 12px;
} overflow-y: auto;
}
.metadata-section { .metadata-section {
padding: var(--spacing-md); padding: 12px;
background-color: var(--color-bg-tertiary); background-color: var(--bg-surface);
border: 1px solid var(--color-border); border: 1px solid var(--border);
border-radius: var(--radius-lg); border-radius: 8px;
} }
.metadata-section-title { .metadata-section-title {
font-size: 0.85rem; font-size: 0.85rem;
font-weight: 700; font-weight: 700;
color: var(--color-text-secondary); color: var(--text-secondary);
text-transform: uppercase; text-transform: uppercase;
margin-bottom: var(--spacing-md); margin-bottom: 12px;
letter-spacing: 0.3px; letter-spacing: 0.3px;
} }
.metadata-row { .metadata-row {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--spacing-xs); gap: 4px;
margin-bottom: var(--spacing-sm); margin-bottom: 8px;
padding-bottom: var(--spacing-sm); padding-bottom: 8px;
border-bottom: 1px solid var(--color-border); border-bottom: 1px solid var(--border);
} }
.metadata-row:last-child { .metadata-row:last-child {
margin-bottom: 0; margin-bottom: 0;
padding-bottom: 0; padding-bottom: 0;
border-bottom: none; border-bottom: none;
} }
.metadata-label { .metadata-label {
font-size: 0.75rem; font-size: 0.75rem;
color: var(--color-text-tertiary); color: var(--text-tertiary);
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.2px; letter-spacing: 0.2px;
} }
.metadata-value { .metadata-value {
font-size: 0.9rem; font-size: 0.9rem;
color: var(--color-text-primary); color: var(--text-primary);
font-weight: 500; font-weight: 500;
} }
.metadata-editable { .metadata-editable {
display: flex; display: flex;
gap: var(--spacing-sm); gap: 8px;
flex-direction: column; flex-direction: column;
} }
.metadata-textarea { .metadata-textarea {
width: 100%; width: 100%;
padding: var(--spacing-sm); padding: 8px;
background-color: var(--color-bg-primary); background-color: var(--bg-base);
border: 1px solid var(--color-border); border: 1px solid var(--border);
border-radius: var(--radius-md); border-radius: 6px;
color: var(--color-text-primary); color: var(--text-primary);
font-family: 'Courier New', monospace; font-family: 'Courier New', monospace;
font-size: 0.85rem; font-size: 0.85rem;
resize: vertical; resize: vertical;
min-height: 80px; min-height: 80px;
} }
.metadata-textarea:focus { .metadata-textarea:focus {
outline: none; outline: none;
border-color: var(--color-accent-primary); border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(233, 69, 96, 0.1); box-shadow: 0 0 0 3px rgba(233, 69, 96, 0.1);
} }
.sidebar { .tags-list {
overflow-y: auto; display: flex;
} flex-wrap: wrap;
gap: 8px;
margin-bottom: 12px;
}
.tags-list { .tag-badge {
display: flex; display: inline-flex;
flex-wrap: wrap; align-items: center;
gap: var(--spacing-sm); gap: 4px;
margin-bottom: var(--spacing-md); padding: 4px 10px;
} background-color: var(--bg-base);
border: 1px solid var(--border);
border-radius: 20px;
font-size: 0.8rem;
color: var(--text-secondary);
cursor: pointer;
transition: all 120ms ease;
}
.tag-badge { .tag-badge:hover {
display: inline-flex; border-color: var(--accent);
align-items: center; color: var(--accent);
gap: var(--spacing-xs); }
padding: 4px 10px;
background-color: var(--color-bg-primary);
border: 1px solid var(--color-border);
border-radius: 20px;
font-size: 0.8rem;
color: var(--color-text-secondary);
cursor: pointer;
transition: all var(--transition-fast);
}
.tag-badge:hover { .tag-remove {
border-color: var(--color-accent-primary); display: flex;
color: var(--color-accent-primary); align-items: center;
} justify-content: center;
cursor: pointer;
font-weight: bold;
}
.tag-remove { .edit-controls {
display: flex; display: flex;
align-items: center; gap: 8px;
justify-content: center; margin-top: 12px;
cursor: pointer; }
font-weight: bold;
}
.edit-controls { @media (max-width: 768px) {
display: flex; .player-container {
gap: var(--spacing-sm); grid-template-columns: 1fr;
margin-top: var(--spacing-md); height: auto;
} }
.back-button { .metadata-panel {
display: flex; display: grid;
align-items: center; grid-template-columns: 1fr 1fr;
gap: var(--spacing-sm); }
padding: var(--spacing-sm) var(--spacing-md);
background-color: var(--color-bg-tertiary);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
color: var(--color-text-secondary);
cursor: pointer;
transition: all var(--transition-fast);
}
.back-button:hover { .video-container {
border-color: var(--color-accent-primary); height: 400px;
color: var(--color-accent-primary); }
} }
</style>
@media (max-width: 768px) {
.player-container {
grid-template-columns: 1fr;
height: auto;
}
.metadata-panel {
display: grid;
grid-template-columns: 1fr 1fr;
}
.video-container {
height: 400px;
}
}
</style>
</head> </head>
<body> <body>
<div class="app-container">
<!-- Header -->
<header class="header">
<div class="header-logo">
<div class="header-logo-icon">D</div>
<span>WILD DRAGON</span>
</div>
<nav class="header-nav">
<div class="nav-item" data-page="assets">Assets</div>
<div class="nav-item" data-page="capture">Capture</div>
<div class="nav-item" data-page="projects">Projects</div>
</nav>
</header>
<!-- Main Content --> <div class="wd-shell" style="display:flex;min-height:100vh;">
<div class="main-content"> <nav class="wd-sidebar" aria-label="Main navigation">
<div class="content-area"> <div class="wd-sidebar-header">
<div class="content-main"> <img src="img/dragon-logo.png?v=1" alt="Dragonflight" style="width:18px;height:18px;">
<button class="back-button" onclick="goBack()"> <span class="wd-sidebar-brand">Dragonflight</span>
<span></span> Back to Assets </div>
</button> <div class="wd-sidebar-nav">
<a href="home.html" class="wd-nav-item">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 7l6-5 6 5v7a1 1 0 0 1-1 1h-3v-5H6v5H3a1 1 0 0 1-1-1z"/></svg>
Home
</a>
<a href="index.html" class="wd-nav-item">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1" y="1" width="6" height="6" rx="1"/><rect x="9" y="1" width="6" height="6" rx="1"/><rect x="1" y="9" width="6" height="6" rx="1"/><rect x="9" y="9" width="6" height="6" rx="1"/></svg>
Library
</a>
<a href="projects.html" class="wd-nav-item">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M1 4a1 1 0 0 1 1-1h4l2 2h5a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1z"/></svg>
Projects
</a>
<a href="upload.html" class="wd-nav-item">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M8 11V3M5 6l3-3 3 3"/><path d="M2 13h12"/></svg>
Ingest
</a>
<a href="recorders.html" class="wd-nav-item">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1" y="4" width="10" height="8" rx="1"/><path d="M11 7l4-2v6l-4-2"/></svg>
Recorders
</a>
<a href="capture.html" class="wd-nav-item">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="3"/><circle cx="8" cy="8" r="6.5"/></svg>
Capture
</a>
<a href="jobs.html" class="wd-nav-item">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 4h12M2 8h8M2 12h5"/></svg>
Jobs
</a>
<a href="edit.html" class="wd-nav-item">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 13l1.5-3.5L11 2l3 3-7.5 7.5L3 14zM10 3l3 3"/></svg>
Editor
</a>
<div class="wd-sidebar-section">Admin</div>
<a href="users.html" class="wd-nav-item">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="6" cy="5" r="2.5"/><path d="M1 13c0-2.8 2.2-5 5-5s5 2.2 5 5"/><circle cx="12" cy="5" r="2"/><path d="M15 12c0-1.9-1.3-3.5-3-4"/></svg>
Users
</a>
<a href="tokens.html" class="wd-nav-item">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="6" cy="10" r="3.5"/><path d="M8.7 7.3L13 3M11.5 3.5l1.5 1.5M13.5 2.5l1 1"/></svg>
Tokens
</a>
</div>
<div class="wd-sidebar-footer">
<div class="wd-sidebar-user">
<div class="wd-sidebar-user-avatar" id="userAvatar">?</div>
<div class="wd-sidebar-user-info">
<div class="wd-sidebar-user-name" id="userName"></div>
<div class="wd-sidebar-user-role" id="userRole"></div>
</div>
<button class="wd-btn wd-btn--ghost wd-btn--sm wd-btn--icon wd-sidebar-user-logout" id="logoutBtn" title="Sign out">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" width="14" height="14"><path d="M10 8H3M6 5l-3 3 3 3"/><path d="M7 3h5a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1H7"/></svg>
</button>
</div>
</div>
</nav>
<div class="player-container"> <div style="flex:1;display:flex;flex-direction:column;">
<!-- Main Player --> <header class="wd-topbar">
<div class="player-main"> <div class="wd-topbar-left">
<div class="video-container"> <span class="page-title">Player</span>
<video class="video-player" id="videoPlayer" controls></video> </div>
</div> <div class="wd-topbar-right">
</div> <button class="wd-btn wd-btn--ghost wd-btn--sm" onclick="goBack()">← Back to Library</button>
</div>
</header>
<!-- Sidebar --> <div style="flex:1;overflow:auto;padding:16px;">
<div class="sidebar"> <div class="player-container">
<!-- File Info --> <!-- Main Player -->
<div class="metadata-section"> <div class="player-main">
<div class="metadata-section-title">File Information</div> <div class="video-container">
<div class="metadata-row"> <video class="video-player" id="videoPlayer" controls></video>
<div class="metadata-label">Filename</div> </div>
<div class="metadata-value" id="metaFilename"></div>
</div>
<div class="metadata-row">
<div class="metadata-label">Format</div>
<div class="metadata-value" id="metaFormat"></div>
</div>
<div class="metadata-row">
<div class="metadata-label">Codec</div>
<div class="metadata-value" id="metaCodec"></div>
</div>
<div class="metadata-row">
<div class="metadata-label">Resolution</div>
<div class="metadata-value" id="metaResolution"></div>
</div>
<div class="metadata-row">
<div class="metadata-label">Framerate</div>
<div class="metadata-value" id="metaFps"></div>
</div>
<div class="metadata-row">
<div class="metadata-label">Duration</div>
<div class="metadata-value" id="metaDuration"></div>
</div>
<div class="metadata-row">
<div class="metadata-label">File Size</div>
<div class="metadata-value" id="metaFileSize"></div>
</div>
<div class="metadata-row">
<div class="metadata-label">Status</div>
<div class="metadata-value" id="metaStatus"></div>
</div>
<div class="metadata-row">
<div class="metadata-label">Captured</div>
<div class="metadata-value" id="metaCreated"></div>
</div>
</div>
<!-- Tags -->
<div class="metadata-section">
<div class="metadata-section-title">Tags</div>
<div class="tags-list" id="tagsList"></div>
<input
type="text"
id="newTagInput"
class="form-input"
placeholder="Add tag..."
style="font-size: 0.85rem;"
>
<button class="btn btn-secondary btn-sm" style="width: 100%; margin-top: var(--spacing-sm);" onclick="addTag()">Add Tag</button>
</div>
<!-- Notes -->
<div class="metadata-section">
<div class="metadata-section-title">Notes</div>
<textarea id="notesInput" class="metadata-textarea" placeholder="Add notes about this asset..."></textarea>
<div class="edit-controls">
<button class="btn btn-primary btn-sm" style="flex: 1;" onclick="saveMetadata()">Save</button>
<button class="btn btn-secondary btn-sm" style="flex: 1;" onclick="resetMetadata()">Reset</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div> </div>
<!-- Status Bar --> <!-- Sidebar -->
<footer class="status-bar"> <div class="metadata-panel">
<div class="status-item"> <!-- File Info -->
<span class="status-indicator" id="statusIndicator"></span> <div class="metadata-section">
<span id="statusText">Connected</span> <div class="metadata-section-title">File Information</div>
<div class="metadata-row">
<div class="metadata-label">Filename</div>
<div class="metadata-value" id="metaFilename"></div>
</div> </div>
</footer> <div class="metadata-row">
<div class="metadata-label">Format</div>
<div class="metadata-value" id="metaFormat"></div>
</div>
<div class="metadata-row">
<div class="metadata-label">Codec</div>
<div class="metadata-value" id="metaCodec"></div>
</div>
<div class="metadata-row">
<div class="metadata-label">Resolution</div>
<div class="metadata-value" id="metaResolution"></div>
</div>
<div class="metadata-row">
<div class="metadata-label">Framerate</div>
<div class="metadata-value" id="metaFps"></div>
</div>
<div class="metadata-row">
<div class="metadata-label">Duration</div>
<div class="metadata-value" id="metaDuration"></div>
</div>
<div class="metadata-row">
<div class="metadata-label">File Size</div>
<div class="metadata-value" id="metaFileSize"></div>
</div>
<div class="metadata-row">
<div class="metadata-label">Status</div>
<div class="metadata-value" id="metaStatus"></div>
</div>
<div class="metadata-row">
<div class="metadata-label">Captured</div>
<div class="metadata-value" id="metaCreated"></div>
</div>
</div>
<!-- Tags -->
<div class="metadata-section">
<div class="metadata-section-title">Tags</div>
<div class="tags-list" id="tagsList"></div>
<input
type="text"
id="newTagInput"
class="form-input"
placeholder="Add tag..."
style="font-size: 0.85rem;"
>
<button class="wd-btn wd-btn--secondary wd-btn--sm" style="width: 100%; margin-top: 8px;" onclick="addTag()">Add Tag</button>
</div>
<!-- Notes -->
<div class="metadata-section">
<div class="metadata-section-title">Notes</div>
<textarea id="notesInput" class="metadata-textarea" placeholder="Add notes about this asset..."></textarea>
<div class="edit-controls">
<button class="wd-btn wd-btn--primary wd-btn--sm" style="flex: 1;" onclick="saveMetadata()">Save</button>
<button class="wd-btn wd-btn--secondary wd-btn--sm" style="flex: 1;" onclick="resetMetadata()">Reset</button>
</div>
</div>
</div>
</div>
</div> </div>
</div>
</div>
<script src="/js/api.js?v=6"></script> <script src="/js/api.js?v=6"></script>
<script src="/js/topbar-strip.js?v=1"></script> <script src="/js/topbar-strip.js?v=1"></script>
<script> <script src="js/auth-guard.js"></script>
// ============================================================ <script>
// STATE MANAGEMENT // ============================================================
// ============================================================ // STATE MANAGEMENT
// ============================================================
let playerState = { let playerState = {
assetId: null, assetId: null,
asset: null, asset: null,
tags: [], tags: [],
notes: '', notes: '',
originalTags: [], originalTags: [],
originalNotes: '', originalNotes: '',
}; };
// ============================================================ // ============================================================
// INITIALIZATION // HELPERS
// ============================================================ // ============================================================
document.addEventListener('DOMContentLoaded', () => { function formatDuration(seconds) {
setupEventListeners(); if (!seconds) return '—';
const params = new URLSearchParams(window.location.search); const h = Math.floor(seconds / 3600);
playerState.assetId = params.get('id'); const m = Math.floor((seconds % 3600) / 60);
const s = Math.floor(seconds % 60);
if (h > 0) return `${h}:${String(m).padStart(2,'0')}:${String(s).padStart(2,'0')}`;
return `${m}:${String(s).padStart(2,'0')}`;
}
if (playerState.assetId) { function formatFileSize(bytes) {
loadAsset(); if (!bytes) return '—';
} else { const units = ['B','KB','MB','GB','TB'];
document.getElementById('statusText').textContent = 'No asset selected'; let i = 0;
} let val = bytes;
}); while (val >= 1024 && i < units.length - 1) { val /= 1024; i++; }
return `${val.toFixed(1)} ${units[i]}`;
}
function setupEventListeners() { function getStatusBadgeClass(status) {
document.getElementById('newTagInput').addEventListener('keypress', (e) => { switch (status) {
if (e.key === 'Enter') { case 'recording': return 'wd-badge wd-badge--bad';
addTag(); case 'ready': return 'wd-badge wd-badge--good';
e.preventDefault(); case 'processing':return 'wd-badge wd-badge--warn';
} case 'error': return 'wd-badge wd-badge--bad';
}); default: return 'wd-badge';
}
}
document.querySelectorAll('[data-page]').forEach(el => { function getStatusLabel(status) {
el.addEventListener('click', (e) => navigateTo(e.target.dataset.page)); switch (status) {
}); case 'recording': return 'Recording';
case 'ready': return 'Ready';
case 'processing': return 'Processing';
case 'error': return 'Error';
default: return status || '—';
}
}
// ============================================================
// INITIALIZATION
// ============================================================
document.addEventListener('DOMContentLoaded', () => {
setupEventListeners();
const params = new URLSearchParams(window.location.search);
playerState.assetId = params.get('id');
if (playerState.assetId) {
loadAsset();
}
});
function setupEventListeners() {
document.getElementById('newTagInput').addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
addTag();
e.preventDefault();
}
});
}
async function loadAsset() {
try {
const result = await getAsset(playerState.assetId);
if (result.success) {
playerState.asset = result.data;
playerState.tags = result.data.tags || [];
playerState.notes = result.data.notes || '';
playerState.originalTags = [...playerState.tags];
playerState.originalNotes = playerState.notes;
renderAsset();
// Load video stream
const streamResult = await getAssetStreamUrl(playerState.assetId);
if (streamResult.success) {
document.getElementById('videoPlayer').src = streamResult.data.url;
} }
}
} catch (error) {
console.error('Error loading asset:', error);
}
}
async function loadAsset() { // ============================================================
try { // RENDERING
const result = await getAsset(playerState.assetId); // ============================================================
if (result.success) { function renderAsset() {
playerState.asset = result.data; const asset = playerState.asset;
playerState.tags = result.data.tags || [];
playerState.notes = result.data.notes || '';
playerState.originalTags = [...playerState.tags];
playerState.originalNotes = playerState.notes;
renderAsset(); document.getElementById('metaFilename').textContent = asset.filename;
document.getElementById('metaFormat').textContent = asset.format || '—';
document.getElementById('metaCodec').textContent = asset.codec || '—';
document.getElementById('metaResolution').textContent = asset.resolution || '—';
document.getElementById('metaFps').textContent = asset.fps ? `${asset.fps} fps` : '—';
document.getElementById('metaDuration').textContent = formatDuration(asset.duration);
document.getElementById('metaFileSize').textContent = formatFileSize(asset.file_size || 0);
document.getElementById('metaStatus').innerHTML =
`<span class="${getStatusBadgeClass(asset.status)}">${getStatusLabel(asset.status)}</span>`;
document.getElementById('metaCreated').textContent = new Date(asset.created_at).toLocaleDateString();
// Load video stream renderTags();
const streamResult = await getAssetStreamUrl(playerState.assetId); document.getElementById('notesInput').value = playerState.notes;
if (streamResult.success) {
document.getElementById('videoPlayer').src = streamResult.data.url;
}
} else {
document.getElementById('statusText').textContent = 'Failed to load asset';
}
} catch (error) {
console.error('Error loading asset:', error);
document.getElementById('statusText').textContent = 'Error loading asset';
}
}
// ============================================================ document.title = `${asset.filename} — Dragonflight Player`;
// RENDERING }
// ============================================================
function renderAsset() { function renderTags() {
const asset = playerState.asset; const container = document.getElementById('tagsList');
container.innerHTML = '';
document.getElementById('metaFilename').textContent = asset.filename; playerState.tags.forEach((tag, index) => {
document.getElementById('metaFormat').textContent = asset.format || '—'; const badge = document.createElement('div');
document.getElementById('metaCodec').textContent = asset.codec || '—'; badge.className = 'tag-badge';
document.getElementById('metaResolution').textContent = asset.resolution || '—';
document.getElementById('metaFps').textContent = asset.fps ? `${asset.fps} fps` : '—';
document.getElementById('metaDuration').textContent = formatDuration(asset.duration);
document.getElementById('metaFileSize').textContent = formatFileSize(asset.file_size || 0);
document.getElementById('metaStatus').innerHTML =
`<span class="badge ${getStatusBadgeClass(asset.status)}">${getStatusLabel(asset.status)}</span>`;
document.getElementById('metaCreated').textContent = new Date(asset.created_at).toLocaleDateString();
renderTags(); const tagSpan = document.createElement('span');
document.getElementById('notesInput').value = playerState.notes; tagSpan.textContent = tag;
document.title = `${asset.filename} - Wild Dragon Player`; const removeSpan = document.createElement('span');
} removeSpan.className = 'tag-remove';
removeSpan.textContent = '×';
removeSpan.onclick = () => removeTag(index);
function renderTags() { badge.appendChild(tagSpan);
const container = document.getElementById('tagsList'); badge.appendChild(removeSpan);
container.innerHTML = ''; container.appendChild(badge);
});
}
playerState.tags.forEach((tag, index) => { // ============================================================
const badge = document.createElement('div'); // METADATA EDITING
badge.className = 'tag-badge'; // ============================================================
const tagSpan = document.createElement('span'); function addTag() {
tagSpan.textContent = tag; const input = document.getElementById('newTagInput');
const tag = input.value.trim().toLowerCase();
const removeSpan = document.createElement('span'); if (tag && !playerState.tags.includes(tag)) {
removeSpan.className = 'tag-remove'; playerState.tags.push(tag);
removeSpan.textContent = '×'; renderTags();
removeSpan.onclick = () => removeTag(index); input.value = '';
}
}
badge.appendChild(tagSpan); function removeTag(index) {
badge.appendChild(removeSpan); playerState.tags.splice(index, 1);
container.appendChild(badge); renderTags();
}); }
}
// ============================================================ async function saveMetadata() {
// METADATA EDITING if (!playerState.assetId) return;
// ============================================================
function addTag() { playerState.notes = document.getElementById('notesInput').value;
const input = document.getElementById('newTagInput');
const tag = input.value.trim().toLowerCase();
if (tag && !playerState.tags.includes(tag)) { try {
playerState.tags.push(tag); const result = await updateAsset(playerState.assetId, {
renderTags(); tags: playerState.tags,
input.value = ''; notes: playerState.notes,
} });
}
function removeTag(index) { if (result.success) {
playerState.tags.splice(index, 1); playerState.originalTags = [...playerState.tags];
renderTags(); playerState.originalNotes = playerState.notes;
} }
} catch (error) {
console.error('Error saving metadata:', error);
}
}
async function saveMetadata() { function resetMetadata() {
if (!playerState.assetId) return; playerState.tags = [...playerState.originalTags];
playerState.notes = playerState.originalNotes;
renderTags();
document.getElementById('notesInput').value = playerState.notes;
}
playerState.notes = document.getElementById('notesInput').value; // ============================================================
// NAVIGATION
// ============================================================
try { function navigateTo(page) {
const result = await updateAsset(playerState.assetId, { if (page === 'assets') {
tags: playerState.tags, window.location.href = '/index.html';
notes: playerState.notes, } else if (page === 'capture') {
}); window.location.href = '/capture.html';
}
}
if (result.success) { function goBack() {
playerState.originalTags = [...playerState.tags]; window.location.href = '/index.html';
playerState.originalNotes = playerState.notes; }
document.getElementById('statusText').textContent = 'Changes saved'; </script>
setTimeout(() => {
document.getElementById('statusText').textContent = 'Connected';
}, 2000);
} else {
document.getElementById('statusText').textContent = 'Failed to save changes';
}
} catch (error) {
console.error('Error saving metadata:', error);
document.getElementById('statusText').textContent = 'Error saving changes';
}
}
function resetMetadata() {
playerState.tags = [...playerState.originalTags];
playerState.notes = playerState.originalNotes;
renderTags();
document.getElementById('notesInput').value = playerState.notes;
}
// ============================================================
// NAVIGATION
// ============================================================
function navigateTo(page) {
if (page === 'assets') {
window.location.href = '/index.html';
} else if (page === 'capture') {
window.location.href = '/capture.html';
}
}
function goBack() {
window.location.href = '/index.html';
}
</script>
</body> </body>
</html> </html>