FEAT: Initial Upload Page
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
package views
|
||||
|
||||
import "thamanyah/cms/v2/internal/views/layouts"
|
||||
|
||||
templ VideoUpload() {
|
||||
@layouts.Layout("Upload Video · Thamanyah CMS") {
|
||||
<h1>Upload a video</h1>
|
||||
<p>Provide the video file along with its metadata.</p>
|
||||
<form id="upload-form">
|
||||
<div class="field">
|
||||
<label for="title">Title</label>
|
||||
<input type="text" id="title" name="title" required/>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="description">Description</label>
|
||||
<textarea id="description" name="description" rows="4"></textarea>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="category">Category</label>
|
||||
<select id="category" name="category">
|
||||
<option value="documentary">Documentary</option>
|
||||
<option value="news">News</option>
|
||||
<option value="entertainment">Entertainment</option>
|
||||
<option value="podcast">Podcast</option>
|
||||
<option value="other">Other</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="tags">Tags</label>
|
||||
<input type="text" id="tags" name="tags" placeholder="comma, separated, tags"/>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="video">Video file</label>
|
||||
<input type="file" id="video" name="video" accept="video/*" required/>
|
||||
</div>
|
||||
<progress id="upload-progress" value="0" max="100"></progress>
|
||||
<button type="submit">Upload</button>
|
||||
</form>
|
||||
<div id="upload-result"></div>
|
||||
<script>
|
||||
document.getElementById('upload-form').addEventListener('submit', function (evt) {
|
||||
evt.preventDefault();
|
||||
|
||||
var form = evt.target;
|
||||
var fileInput = document.getElementById('video');
|
||||
var file = fileInput.files[0];
|
||||
var resultEl = document.getElementById('upload-result');
|
||||
var progressEl = document.getElementById('upload-progress');
|
||||
var button = form.querySelector('button[type="submit"]');
|
||||
|
||||
function showError(message) {
|
||||
resultEl.innerHTML = '<div class="result result-error"><p>' + message + '</p></div>';
|
||||
}
|
||||
|
||||
if (!file) {
|
||||
showError('A video file is required.');
|
||||
return;
|
||||
}
|
||||
|
||||
var contentType = file.type || 'application/octet-stream';
|
||||
|
||||
button.disabled = true;
|
||||
progressEl.value = 0;
|
||||
resultEl.innerHTML = '';
|
||||
|
||||
fetch('/videos/presign', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ fileName: file.name, contentType: contentType })
|
||||
}).then(function (res) {
|
||||
return res.json().then(function (body) {
|
||||
if (!res.ok) {
|
||||
throw new Error(body.error || 'Could not prepare upload.');
|
||||
}
|
||||
return body;
|
||||
});
|
||||
}).then(function (presigned) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('PUT', presigned.uploadUrl);
|
||||
xhr.setRequestHeader('Content-Type', contentType);
|
||||
xhr.upload.addEventListener('progress', function (e) {
|
||||
if (e.lengthComputable) {
|
||||
progressEl.value = Math.round((e.loaded / e.total) * 100);
|
||||
}
|
||||
});
|
||||
xhr.onload = function () {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
resolve(presigned.key);
|
||||
} else {
|
||||
reject(new Error('Upload to storage failed.'));
|
||||
}
|
||||
};
|
||||
xhr.onerror = function () {
|
||||
reject(new Error('Upload to storage failed.'));
|
||||
};
|
||||
xhr.send(file);
|
||||
});
|
||||
}).then(function (key) {
|
||||
return fetch('/videos', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
title: form.title.value.trim(),
|
||||
description: form.description.value.trim(),
|
||||
category: form.category.value,
|
||||
tags: form.tags.value.trim(),
|
||||
fileName: file.name,
|
||||
key: key
|
||||
})
|
||||
});
|
||||
}).then(function (res) {
|
||||
return res.text().then(function (html) {
|
||||
resultEl.innerHTML = html;
|
||||
});
|
||||
}).catch(function (err) {
|
||||
showError(err.message);
|
||||
}).finally(function () {
|
||||
button.disabled = false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
}
|
||||
}
|
||||
|
||||
templ VideoUploadSuccess(v VideoMetadata) {
|
||||
<div class="result result-success">
|
||||
<h2>Upload complete</h2>
|
||||
<dl>
|
||||
<dt>Title</dt>
|
||||
<dd>{ v.Title }</dd>
|
||||
if v.Description != "" {
|
||||
<dt>Description</dt>
|
||||
<dd>{ v.Description }</dd>
|
||||
}
|
||||
<dt>Category</dt>
|
||||
<dd>{ v.Category }</dd>
|
||||
if v.Tags != "" {
|
||||
<dt>Tags</dt>
|
||||
<dd>{ v.Tags }</dd>
|
||||
}
|
||||
<dt>File</dt>
|
||||
<dd>
|
||||
{ v.FileName } ({ formatSize(v.SizeBytes) })
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
}
|
||||
|
||||
templ VideoUploadError(message string) {
|
||||
<div class="result result-error">
|
||||
<p style="color:green">{ message }</p>
|
||||
</div>
|
||||
}
|
||||
Reference in New Issue
Block a user