mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Add test harness (window.__bitvidTest__), data-testid attributes, relay override support, and Playwright fixtures so agents can programmatically login, seed relay data, and inspect app state during E2E testing. - js/testHarness.js: exposes loginWithNsec, getAppState, relay overrides - data-testid attrs on login/upload/video modals, header buttons, feed - simple-relay.mjs: HTTP seeding API (POST /seed, GET /events, /health) - bitvidTestFixture.ts: reusable Playwright fixture with mock relay - agent-testability.spec.ts: E2E tests validating the infrastructure https://claude.ai/code/session_01XnrszNmdqrAGd8g8qhAn4G
402 lines
22 KiB
HTML
402 lines
22 KiB
HTML
<!-- components/upload-modal.html -->
|
|
<div
|
|
id="uploadModal"
|
|
class="bv-modal hidden items-start justify-center md:items-center"
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-labelledby="uploadModalTitle"
|
|
aria-describedby="uploadModalDescription"
|
|
data-ds="new"
|
|
data-testid="upload-modal"
|
|
>
|
|
<div class="bv-modal-backdrop" data-dismiss></div>
|
|
|
|
<!-- Modal Container -->
|
|
<div class="modal-sheet max-h-modal-shell overflow-hidden flex flex-col w-full max-w-2xl" tabindex="-1">
|
|
<!-- Top Bar -->
|
|
<div class="modal-header transition-transform duration-150">
|
|
<h2 id="uploadModalTitle" class="text-xl font-bold text-text">
|
|
Share a Video
|
|
</h2>
|
|
<button
|
|
id="closeUploadModal"
|
|
class="modal-close"
|
|
type="button"
|
|
aria-label="Close upload modal"
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-6 w-6"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M6 18L18 6M6 6l12 12"
|
|
/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Main Scrollable Body -->
|
|
<div class="modal-body space-y-6 overflow-y-auto flex-1 min-h-0 custom-scrollbar p-6">
|
|
|
|
<!-- Source Selector -->
|
|
<div class="flex justify-center mb-2">
|
|
<div
|
|
class="inline-flex items-center rounded-full bg-surface-alt p-1 text-sm shadow-inner"
|
|
role="group"
|
|
aria-label="Upload source selector"
|
|
>
|
|
<button
|
|
type="button"
|
|
id="btn-mode-upload"
|
|
class="rounded-full px-6 py-1.5 font-medium transition-colors text-text bg-surface shadow-sm"
|
|
aria-pressed="true"
|
|
>
|
|
Upload File
|
|
</button>
|
|
<button
|
|
type="button"
|
|
id="btn-mode-external"
|
|
class="rounded-full px-6 py-1.5 font-medium transition-colors text-muted hover:text-text"
|
|
aria-pressed="false"
|
|
>
|
|
External Link
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<form id="unifiedUploadForm" class="space-y-6">
|
|
|
|
<!-- Source: Upload File -->
|
|
<div id="section-source-upload" class="space-y-4">
|
|
<div class="relative">
|
|
<label
|
|
for="input-file"
|
|
class="flex flex-col items-center justify-center w-full h-32 border-2 border-dashed rounded-lg cursor-pointer bg-surface/30 border-border hover:bg-surface/50 transition-colors"
|
|
>
|
|
<div class="flex flex-col items-center justify-center pt-5 pb-6">
|
|
<svg class="w-8 h-8 mb-3 text-muted" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 16">
|
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 13h3a3 3 0 0 0 0-6h-.025A5.56 5.56 0 0 0 16 6.5 5.5 5.5 0 0 0 5.207 5.021C5.137 5.017 5.071 5 5 5a4 4 0 0 0 0 8h2.167M10 15V6m0 0L8 8m2-2 2 2"/>
|
|
</svg>
|
|
<p class="mb-2 text-sm text-text"><span class="font-semibold">Click to upload</span> or drag and drop</p>
|
|
<p class="text-xs text-muted">MP4, WebM, MKV, MOV (Max 2GB recommended)</p>
|
|
</div>
|
|
<input id="input-file" type="file" class="hidden" accept="video/*,.m3u8,.ts,.mp4,.webm,.mov,.mkv,.mpg,.mpeg" />
|
|
</label>
|
|
</div>
|
|
|
|
<!-- Upload Progress -->
|
|
<div id="upload-progress-container" class="space-y-2 hidden">
|
|
<div class="flex justify-between text-xs">
|
|
<span id="upload-status-text" class="text-text font-medium">Uploading...</span>
|
|
<span id="upload-percent-text" class="text-muted">0%</span>
|
|
</div>
|
|
<progress
|
|
id="input-progress"
|
|
class="progress progress--surface w-full"
|
|
max="100"
|
|
value="0"
|
|
></progress>
|
|
</div>
|
|
|
|
<!-- Generated Links (Results) -->
|
|
<div id="upload-results-container" class="space-y-3 bg-surface/30 p-3 rounded-lg border border-border/50 hidden">
|
|
<h4 class="text-xs font-semibold text-text uppercase tracking-wider">Generated Links</h4>
|
|
|
|
<div class="space-y-1">
|
|
<label class="text-xs text-muted">Video URL</label>
|
|
<input id="result-video-url" type="text" readonly class="input input-sm text-xs bg-surface-alt font-mono text-muted" placeholder="Pending..." />
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-3">
|
|
<div class="space-y-1">
|
|
<label class="text-xs text-muted">Magnet URI</label>
|
|
<input id="result-magnet" type="text" readonly class="input input-sm text-xs bg-surface-alt font-mono text-muted" placeholder="Pending..." />
|
|
</div>
|
|
<div class="space-y-1">
|
|
<label class="text-xs text-muted">Torrent URL (xs)</label>
|
|
<input id="result-torrent-url" type="text" readonly class="input input-sm text-xs bg-surface-alt font-mono text-muted" placeholder="Pending..." />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Storage Configuration Toggle -->
|
|
<div class="border-t border-border/50 pt-2">
|
|
<button
|
|
type="button"
|
|
id="btn-storage-settings"
|
|
class="flex items-center gap-2 text-xs text-muted hover:text-text transition-colors"
|
|
aria-expanded="false"
|
|
>
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
</svg>
|
|
<span>Storage Status</span>
|
|
</button>
|
|
|
|
<!-- Storage Settings Container -->
|
|
<div id="section-storage-settings" class="hidden mt-3 space-y-4 bg-surface-alt/50 p-4 rounded-lg border border-border/50">
|
|
|
|
<!-- Read-Only Summary (for configured storage) -->
|
|
<div id="storage-summary-view" class="hidden space-y-3">
|
|
<div class="flex items-center justify-between">
|
|
<h4 class="text-sm font-semibold text-text">Active Storage</h4>
|
|
<div class="flex items-center gap-2">
|
|
<span id="storage-lock-status" class="text-xs text-muted">Locked 🔒</span>
|
|
<button type="button" id="btn-storage-unlock" class="text-xs text-accent hover:underline hidden">Unlock</button>
|
|
<button type="button" id="btn-manage-storage" class="text-xs text-muted hover:text-text hover:underline">Manage</button>
|
|
</div>
|
|
</div>
|
|
<div class="grid grid-cols-3 gap-2 text-xs">
|
|
<div class="min-w-0">
|
|
<span class="block text-muted">Provider</span>
|
|
<span id="summary-provider" class="block font-medium text-text truncate" tabindex="0">--</span>
|
|
</div>
|
|
<div class="min-w-0">
|
|
<span class="block text-muted">Bucket</span>
|
|
<span id="summary-bucket" class="block font-medium text-text truncate" tabindex="0">--</span>
|
|
</div>
|
|
<div class="min-w-0">
|
|
<span class="block text-muted">URL Style</span>
|
|
<span id="summary-url-style" class="block font-medium text-text truncate" tabindex="0">--</span>
|
|
</div>
|
|
</div>
|
|
<p id="summary-copy" class="text-xs text-muted">
|
|
Your videos will be uploaded using this storage provider.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Empty State / Not Configured -->
|
|
<div id="storage-empty-view" class="space-y-4 text-center py-4">
|
|
<p class="text-sm text-muted">
|
|
Storage is not configured.
|
|
</p>
|
|
<button
|
|
type="button"
|
|
id="btn-configure-storage"
|
|
class="btn btn-sm btn-secondary"
|
|
>
|
|
Configure Storage
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Source: External Link -->
|
|
<div id="section-source-external" class="space-y-4 hidden">
|
|
<div class="space-y-2">
|
|
<label for="input-url" class="block text-sm font-medium text-text">Video URL</label>
|
|
<input id="input-url" type="url" class="input" placeholder="https://example.com/video.mp4" data-testid="upload-url" />
|
|
<p class="text-xs text-muted">Primary hosted video source (URL-first playback).</p>
|
|
</div>
|
|
<div class="space-y-2">
|
|
<label for="input-magnet" class="block text-sm font-medium text-text">Magnet Link</label>
|
|
<input id="input-magnet" type="text" class="input" placeholder="magnet:?xt=urn:btih:..." data-testid="upload-magnet" />
|
|
<p class="text-xs text-muted">Optional WebTorrent fallback when the URL is unavailable.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<hr class="border-border/50" />
|
|
|
|
<!-- Metadata -->
|
|
<div class="space-y-4">
|
|
<div class="space-y-2">
|
|
<label for="input-title" class="block text-sm font-medium text-text">Title</label>
|
|
<input id="input-title" type="text" required class="input" placeholder="Give your video a title" data-testid="upload-title" />
|
|
</div>
|
|
|
|
<div class="space-y-2">
|
|
<label for="input-description" class="block text-sm font-medium text-text">Description</label>
|
|
<textarea id="input-description" rows="4" class="textarea" placeholder="Tell viewers about your video"></textarea>
|
|
</div>
|
|
|
|
<div class="space-y-2">
|
|
<label for="input-thumbnail" class="block text-sm font-medium text-text">Thumbnail URL</label>
|
|
<div class="flex gap-2">
|
|
<input id="input-thumbnail" type="url" class="input flex-1" placeholder="https://example.com/thumbnail.jpg" />
|
|
<input id="input-thumbnail-file" type="file" class="hidden" accept="image/*" />
|
|
<button
|
|
type="button"
|
|
id="btn-thumbnail-file"
|
|
class="btn-secondary px-3 text-sm whitespace-nowrap"
|
|
aria-label="Upload thumbnail image"
|
|
>
|
|
Browse...
|
|
</button>
|
|
</div>
|
|
<!-- Thumbnail Progress -->
|
|
<div id="thumbnail-progress-container" class="space-y-1 hidden mt-2">
|
|
<div class="flex justify-between text-xs">
|
|
<span id="thumbnail-status-text" class="text-text font-medium">Uploading Thumbnail...</span>
|
|
<span id="thumbnail-percent-text" class="text-muted">0%</span>
|
|
</div>
|
|
<progress
|
|
id="thumbnail-progress"
|
|
class="progress progress--surface w-full h-1.5"
|
|
max="100"
|
|
value="0"
|
|
></progress>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Hashtags -->
|
|
<div class="space-y-2">
|
|
<label class="block text-sm font-medium text-text">Hashtags</label>
|
|
<div
|
|
class="rounded-lg border border-border/70 bg-surface/30 p-3 space-y-3"
|
|
data-nip71-repeater="t"
|
|
>
|
|
<div id="hashtag-list" class="flex flex-wrap gap-2" data-nip71-list="t"></div>
|
|
<div class="flex gap-2">
|
|
<button
|
|
type="button"
|
|
class="btn-ghost px-3 py-1.5 text-xs bg-surface border border-border/50"
|
|
data-nip71-add="t"
|
|
>
|
|
+ Add Tag
|
|
</button>
|
|
</div>
|
|
<template data-nip71-template="t">
|
|
<div class="inline-flex items-center bg-surface-alt rounded-full px-1 border border-border/50" data-nip71-entry="t">
|
|
<span class="text-muted pl-2 text-sm">#</span>
|
|
<input
|
|
type="text"
|
|
data-nip71-field="value"
|
|
placeholder="nostr"
|
|
class="bg-transparent border-none text-sm text-text focus:ring-0 p-1 w-24"
|
|
/>
|
|
<button
|
|
type="button"
|
|
class="text-muted hover:text-critical p-1 rounded-full"
|
|
data-nip71-remove="t"
|
|
aria-label="Remove hashtag"
|
|
>
|
|
×
|
|
</button>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Flags / Toggles -->
|
|
<div class="flex flex-wrap gap-4 py-2">
|
|
<label class="flex items-center gap-2 cursor-pointer select-none">
|
|
<input type="checkbox" id="check-comments" class="checkbox" checked />
|
|
<span class="text-sm text-text">Enable Comments</span>
|
|
</label>
|
|
<label class="flex items-center gap-2 cursor-pointer select-none">
|
|
<input type="checkbox" id="check-nsfw" class="checkbox" />
|
|
<span class="text-sm text-text">NSFW</span>
|
|
</label>
|
|
<label class="flex items-center gap-2 cursor-pointer select-none">
|
|
<input type="checkbox" id="check-kids" class="checkbox" />
|
|
<span class="text-sm text-text">For Kids</span>
|
|
</label>
|
|
</div>
|
|
|
|
<!-- Advanced Options -->
|
|
<div class="border-t border-border/70 pt-4">
|
|
<button
|
|
type="button"
|
|
id="btn-advanced-toggle"
|
|
class="flex items-center gap-2 text-sm font-medium text-text hover:text-accent transition-colors w-full"
|
|
aria-expanded="false"
|
|
>
|
|
<svg
|
|
id="icon-advanced-toggle"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-4 w-4 transform text-muted transition-transform duration-150"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
|
|
</svg>
|
|
<span>Advanced Options</span>
|
|
</button>
|
|
|
|
<div id="section-advanced" class="hidden mt-4 space-y-6 pl-2 border-l-2 border-border/30">
|
|
|
|
<!-- NIP-71 Technical -->
|
|
<div class="grid gap-4 md:grid-cols-2">
|
|
<div class="space-y-2">
|
|
<label class="block text-xs font-medium text-muted-strong">Content Warning</label>
|
|
<input id="input-content-warning" type="text" data-nip71-input="content-warning" class="input text-sm" placeholder="e.g. Nudity" />
|
|
</div>
|
|
<div class="space-y-2">
|
|
<label class="block text-xs font-medium text-muted-strong">Duration (sec)</label>
|
|
<input id="input-duration" type="number" step="1" data-nip71-input="duration" class="input text-sm" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="space-y-2">
|
|
<div class="flex items-center justify-between">
|
|
<label class="block text-xs font-medium text-muted-strong">Summary (NIP-71 Content)</label>
|
|
<label class="flex items-center gap-2 text-xs text-muted cursor-pointer">
|
|
<input type="checkbox" id="check-summary-unlock" class="checkbox h-3 w-3" />
|
|
Customize
|
|
</label>
|
|
</div>
|
|
<textarea id="input-summary" rows="2" data-nip71-input="summary" class="textarea text-sm text-muted" readonly></textarea>
|
|
</div>
|
|
|
|
<!-- IMETA Editor -->
|
|
<div class="space-y-3" data-nip71-repeater="imeta">
|
|
<div class="flex items-center justify-between">
|
|
<h4 class="text-sm font-semibold text-text">Video Variants (IMETA)</h4>
|
|
<button type="button" class="btn-ghost px-2 py-1 text-xs" data-nip71-add="imeta">Add Variant</button>
|
|
</div>
|
|
<div id="imeta-list" class="space-y-4" data-nip71-list="imeta"></div>
|
|
|
|
<!-- IMETA Template -->
|
|
<template data-nip71-template="imeta">
|
|
<article class="rounded-md border border-border/70 bg-surface/50 p-3 space-y-3" data-nip71-entry="imeta">
|
|
<div class="grid gap-2 md:grid-cols-2 text-xs">
|
|
<input type="text" data-nip71-field="m" placeholder="MIME (video/mp4)" class="input" />
|
|
<input type="text" data-nip71-field="dim" placeholder="Dimensions (1920x1080)" class="input" />
|
|
<input type="url" data-nip71-field="url" placeholder="URL" class="input md:col-span-2" />
|
|
<input type="text" data-nip71-field="x" placeholder="Magnet" class="input md:col-span-2" />
|
|
</div>
|
|
<div class="flex justify-end">
|
|
<button type="button" class="text-xs text-critical hover:underline" data-nip71-remove="imeta">Remove Variant</button>
|
|
</div>
|
|
</article>
|
|
</template>
|
|
</div>
|
|
|
|
<!-- Additional Technical Sections (Web Seeds, etc) -->
|
|
<div class="grid gap-3 md:grid-cols-2">
|
|
<div class="space-y-2">
|
|
<label class="block text-xs font-medium text-muted-strong">Web Seed (ws)</label>
|
|
<input id="input-ws" type="url" class="input text-sm" placeholder="https://..." />
|
|
</div>
|
|
<div class="space-y-2">
|
|
<label class="block text-xs font-medium text-muted-strong">Torrent File (xs)</label>
|
|
<input id="input-xs" type="url" class="input text-sm" placeholder="https://..." />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Submit -->
|
|
<div class="pt-4">
|
|
<button type="submit" id="btn-submit" class="btn w-full py-3 text-base font-semibold shadow-md" data-testid="upload-submit">
|
|
Publish Video
|
|
</button>
|
|
<p id="submit-status" class="text-center text-xs text-muted mt-2"></p>
|
|
</div>
|
|
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|