Media Upload Fix
This commit is contained in:
@@ -24,7 +24,7 @@ func (rt *Router) UploadSimulationResource(w http.ResponseWriter, r *http.Reques
|
||||
return
|
||||
}
|
||||
|
||||
err = r.ParseMultipartForm(500 << 20) // 500 MB max memory/file bounds
|
||||
err = r.ParseMultipartForm(32 << 20) // 32 MB max memory bounds, rest spills to disk
|
||||
if err != nil {
|
||||
http.Error(w, "Error parsing form: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script lang="ts">
|
||||
let { simName, resourceName, isEditing, onDelete } = $props();
|
||||
|
||||
let showVideo = $state(false);
|
||||
|
||||
function getResourceUrl(simName: string, resourceName: string) {
|
||||
return `/results/${simName}/${resourceName}`;
|
||||
}
|
||||
@@ -17,7 +19,8 @@
|
||||
return (
|
||||
fname.toLowerCase().endsWith(".mp4") ||
|
||||
fname.toLowerCase().endsWith(".avi") ||
|
||||
fname.toLowerCase().endsWith(".webm")
|
||||
fname.toLowerCase().endsWith(".webm") ||
|
||||
fname.toLowerCase().endsWith(".mkv")
|
||||
);
|
||||
}
|
||||
</script>
|
||||
@@ -28,6 +31,11 @@
|
||||
<a href={getResourceUrl(simName, resourceName)} download class="dl-link"
|
||||
>[Download]</a
|
||||
>
|
||||
{#if isVideo(resourceName)}
|
||||
<button class="watch-link" onclick={() => (showVideo = !showVideo)}>
|
||||
[{showVideo ? "Hide" : "Watch"}]
|
||||
</button>
|
||||
{/if}
|
||||
{#if isEditing}
|
||||
<button
|
||||
class="delete-link"
|
||||
@@ -41,17 +49,14 @@
|
||||
{#if isImage(resourceName)}
|
||||
<img src={getResourceUrl(simName, resourceName)} alt={resourceName} />
|
||||
{:else if isVideo(resourceName)}
|
||||
<video controls>
|
||||
<source
|
||||
src={getResourceUrl(simName, resourceName)}
|
||||
type="video/webm"
|
||||
/>
|
||||
<source
|
||||
src={getResourceUrl(simName, resourceName)}
|
||||
type="video/mp4"
|
||||
/>
|
||||
<a href={getResourceUrl(simName, resourceName)}>Download Video</a>
|
||||
</video>
|
||||
{#if showVideo}
|
||||
<video controls src={getResourceUrl(simName, resourceName)}>
|
||||
<track kind="captions" />
|
||||
<a href={getResourceUrl(simName, resourceName)}
|
||||
>Download Video</a
|
||||
>
|
||||
</video>
|
||||
{/if}
|
||||
{:else}
|
||||
<ul>
|
||||
<li>
|
||||
@@ -94,6 +99,21 @@
|
||||
.dl-link:visited {
|
||||
color: #800080;
|
||||
}
|
||||
.watch-link {
|
||||
font-weight: normal;
|
||||
margin-left: 10px;
|
||||
color: #0000ff;
|
||||
text-decoration: underline;
|
||||
font-size: 14px;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
font-family: inherit;
|
||||
}
|
||||
.watch-link:hover {
|
||||
color: #800080;
|
||||
}
|
||||
.delete-link {
|
||||
font-weight: normal;
|
||||
margin-left: 10px;
|
||||
|
||||
@@ -157,6 +157,8 @@
|
||||
let newName = $state("");
|
||||
let uploadFiles = $state<FileList | null>(null);
|
||||
let saveError = $state("");
|
||||
let uploadProgress = $state(0);
|
||||
let isUploading = $state(false);
|
||||
|
||||
async function handleSave() {
|
||||
if (!simulation) return;
|
||||
@@ -182,32 +184,55 @@
|
||||
|
||||
// 2. Upload Logic
|
||||
if (uploadFiles && uploadFiles.length > 0) {
|
||||
isUploading = true;
|
||||
for (let i = 0; i < uploadFiles.length; i++) {
|
||||
const formData = new FormData();
|
||||
formData.append("file", uploadFiles[i]);
|
||||
const file = uploadFiles[i];
|
||||
uploadProgress = 0;
|
||||
|
||||
const uploadRes = await fetch(`/api/simulations/${id}/upload`, {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
try {
|
||||
const upData: any = await new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", `/api/simulations/${id}/upload`);
|
||||
|
||||
xhr.upload.onprogress = (event) => {
|
||||
if (event.lengthComputable) {
|
||||
uploadProgress = Math.round((event.loaded / event.total) * 100);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onload = () => {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
resolve(JSON.parse(xhr.responseText));
|
||||
} else {
|
||||
reject(xhr.responseText || `Failed to upload: ${file.name}`);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = () => reject(`Network error uploading: ${file.name}`);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
xhr.send(formData);
|
||||
});
|
||||
|
||||
if (!uploadRes.ok) {
|
||||
saveError = `Failed to upload: ${uploadFiles[i].name}`;
|
||||
if (!simulation.resources.includes(upData.filename)) {
|
||||
simulation.resources = [
|
||||
...simulation.resources,
|
||||
upData.filename,
|
||||
];
|
||||
}
|
||||
} catch (err: any) {
|
||||
saveError = err.toString();
|
||||
isUploading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const upData = await uploadRes.json();
|
||||
if (!simulation.resources.includes(upData.filename)) {
|
||||
simulation.resources = [
|
||||
...simulation.resources,
|
||||
upData.filename,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isEditing = false;
|
||||
isUploading = false;
|
||||
uploadFiles = null;
|
||||
uploadProgress = 0;
|
||||
}
|
||||
|
||||
function cancelEdit() {
|
||||
@@ -217,6 +242,8 @@
|
||||
isEditing = false;
|
||||
saveError = "";
|
||||
uploadFiles = null;
|
||||
uploadProgress = 0;
|
||||
isUploading = false;
|
||||
}
|
||||
|
||||
async function handleDelete() {
|
||||
@@ -297,11 +324,15 @@
|
||||
{#if saveError}
|
||||
<p class="error">{saveError}</p>
|
||||
{/if}
|
||||
{#if isUploading}
|
||||
<div class="progress-bar-container">
|
||||
<div class="progress-bar" style={`width: ${uploadProgress}%`}></div>
|
||||
<span class="progress-text">{uploadProgress}%</span>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="form-actions">
|
||||
<button class="save-btn" onclick={handleSave}>Save</button>
|
||||
<button class="cancel-btn" onclick={cancelEdit}
|
||||
>Cancel</button
|
||||
>
|
||||
<button class="save-btn" onclick={handleSave} disabled={isUploading}>Save</button>
|
||||
<button class="cancel-btn" onclick={cancelEdit} disabled={isUploading}>Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -538,11 +569,15 @@
|
||||
background-color: #e0e0e0;
|
||||
font-family: inherit;
|
||||
}
|
||||
.edit-btn:hover,
|
||||
.save-btn:hover,
|
||||
.cancel-btn:hover {
|
||||
.edit-btn:hover:not(:disabled),
|
||||
.save-btn:hover:not(:disabled),
|
||||
.cancel-btn:hover:not(:disabled) {
|
||||
background-color: #d0d0d0;
|
||||
}
|
||||
button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.delete-btn {
|
||||
background-color: #ffcccc;
|
||||
color: #990000;
|
||||
@@ -569,16 +604,39 @@
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.form-group label {
|
||||
.edit-form label {
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
font-weight: bold;
|
||||
min-width: 120px;
|
||||
}
|
||||
.form-group input[type="text"] {
|
||||
.edit-form input[type="text"] {
|
||||
flex: 1;
|
||||
max-width: 400px;
|
||||
padding: 5px;
|
||||
font-family: inherit;
|
||||
border: 1px solid #000;
|
||||
font-family: inherit;
|
||||
}
|
||||
.progress-bar-container {
|
||||
width: 100%;
|
||||
background-color: #ddd;
|
||||
border: 1px solid #000;
|
||||
margin-top: 10px;
|
||||
position: relative;
|
||||
height: 25px;
|
||||
}
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background-color: #4CAF50;
|
||||
transition: width 0.2s ease-in-out;
|
||||
}
|
||||
.progress-text {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
top: 0;
|
||||
left: 0;
|
||||
line-height: 25px;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
}
|
||||
.form-group input[type="file"] {
|
||||
font-family: inherit;
|
||||
|
||||
Reference in New Issue
Block a user