feat: migrate player.html to wd-* design system
This commit is contained in:
parent
9ff80f8cc1
commit
6176791174
1 changed files with 480 additions and 430 deletions
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue