修改自适应
This commit is contained in:
@@ -26,25 +26,83 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
videoListEl.classList.remove('hidden');
|
videoListEl.classList.remove('hidden');
|
||||||
|
|
||||||
if (data.videos.length === 0) {
|
if (data.videos.length === 0) {
|
||||||
videoListEl.innerHTML = '<p style="color: var(--text-secondary); text-align: center; padding: 2rem;">No MP4 videos found in the S3 bucket.</p>';
|
videoListEl.innerHTML = '<p style="color: var(--text-secondary); text-align: center; padding: 2rem;">No videos found.</p>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build a tree structure from S3 keys
|
||||||
|
const tree = {};
|
||||||
data.videos.forEach(key => {
|
data.videos.forEach(key => {
|
||||||
|
const parts = key.split('/');
|
||||||
|
let current = tree;
|
||||||
|
for (let i = 0; i < parts.length; i++) {
|
||||||
|
const part = parts[i];
|
||||||
|
if (!current[part]) {
|
||||||
|
current[part] = (i === parts.length - 1) ? key : {};
|
||||||
|
}
|
||||||
|
if (i < parts.length - 1) {
|
||||||
|
current = current[part];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Recursive function to render the tree
|
||||||
|
const renderTree = (node, container) => {
|
||||||
|
for (const [name, value] of Object.entries(node)) {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
// It's a file
|
||||||
const li = document.createElement('li');
|
const li = document.createElement('li');
|
||||||
li.className = 'video-item';
|
li.className = 'video-item file-item';
|
||||||
|
const ext = name.split('.').pop().toUpperCase();
|
||||||
li.innerHTML = `
|
li.innerHTML = `
|
||||||
<div class="video-icon">
|
<div class="video-icon">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m22 8-6 4 6 4V8Z"/><rect width="14" height="12" x="2" y="6" rx="2" ry="2"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m22 8-6 4 6 4V8Z"/><rect width="14" height="12" x="2" y="6" rx="2" ry="2"/></svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="video-info">
|
<div class="video-info">
|
||||||
<div class="video-title" title="${key}">${key.split('/').pop()}</div>
|
<div class="video-title" title="${value}">${name}</div>
|
||||||
<div class="video-meta">H264 / AAC</div>
|
<div class="video-meta">Video / ${ext}</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
li.addEventListener('click', () => selectVideo(key, li));
|
li.addEventListener('click', (e) => {
|
||||||
videoListEl.appendChild(li);
|
e.stopPropagation();
|
||||||
|
selectVideo(value, li);
|
||||||
});
|
});
|
||||||
|
container.appendChild(li);
|
||||||
|
} else {
|
||||||
|
// It's a folder
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.className = 'folder-item';
|
||||||
|
|
||||||
|
const folderHeader = document.createElement('div');
|
||||||
|
folderHeader.className = 'folder-header';
|
||||||
|
folderHeader.innerHTML = `
|
||||||
|
<div class="folder-icon">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
|
||||||
|
</div>
|
||||||
|
<div class="folder-title" title="${name}">${name}</div>
|
||||||
|
<div class="folder-toggle">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="chevron"><path d="m9 18 6-6-6-6"/></svg>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const subListContainer = document.createElement('ul');
|
||||||
|
subListContainer.className = 'sub-list hidden';
|
||||||
|
|
||||||
|
folderHeader.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
li.classList.toggle('open');
|
||||||
|
subListContainer.classList.toggle('hidden');
|
||||||
|
});
|
||||||
|
|
||||||
|
li.appendChild(folderHeader);
|
||||||
|
renderTree(value, subListContainer);
|
||||||
|
li.appendChild(subListContainer);
|
||||||
|
container.appendChild(li);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
renderTree(tree, videoListEl);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
loadingSpinner.innerHTML = `<p style="color: #ef4444;">Error: ${err.message}</p>`;
|
loadingSpinner.innerHTML = `<p style="color: #ef4444;">Error: ${err.message}</p>`;
|
||||||
|
|||||||
@@ -40,10 +40,15 @@ app.get('/api/videos', async (req, res) => {
|
|||||||
|
|
||||||
const response = await s3Client.send(command);
|
const response = await s3Client.send(command);
|
||||||
|
|
||||||
// Filter out non-mp4 files for this boilerplate
|
// Filter for common video formats
|
||||||
|
const videoExtensions = ['.mp4', '.avi', '.mov', '.mkv', '.webm', '.flv', '.wmv', '.m4v'];
|
||||||
const videos = (response.Contents || [])
|
const videos = (response.Contents || [])
|
||||||
.map(item => item.Key)
|
.map(item => item.Key)
|
||||||
.filter(key => key && key.toLowerCase().endsWith('.mp4'));
|
.filter(key => {
|
||||||
|
if (!key) return false;
|
||||||
|
const lowerKey = key.toLowerCase();
|
||||||
|
return videoExtensions.some(ext => lowerKey.endsWith(ext));
|
||||||
|
});
|
||||||
|
|
||||||
res.json({ videos });
|
res.json({ videos });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user