new file previewer modal
old one is reusing the component, making lots of bugs appear
This commit is contained in:
parent
b811bb6545
commit
1aa4c5e6ae
2 changed files with 446 additions and 155 deletions
379
src/components/modals/FileViewerModal.tsx
Normal file
379
src/components/modals/FileViewerModal.tsx
Normal file
|
@ -0,0 +1,379 @@
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
interface FileType {
|
||||||
|
url: string;
|
||||||
|
type: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FileViewerModalProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
files: FileType | FileType[];
|
||||||
|
modalId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a wrapper component that listens to custom events
|
||||||
|
export const FileViewerModalWrapper: React.FC = () => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [files, setFiles] = useState<FileType[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Listen for custom events to open/close modal and set files
|
||||||
|
const handleShowFiles = (event: CustomEvent) => {
|
||||||
|
const { files } = event.detail;
|
||||||
|
setFiles(Array.isArray(files) ? files : [files]);
|
||||||
|
setIsOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add event listeners
|
||||||
|
window.addEventListener('showFileViewer' as any, handleShowFiles);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('showFileViewer' as any, handleShowFiles);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FileViewerModal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={handleClose}
|
||||||
|
files={files}
|
||||||
|
modalId="file-viewer"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const FileViewerModal: React.FC<FileViewerModalProps> = ({ isOpen, onClose, files, modalId = 'file-viewer' }) => {
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [selectedFile, setSelectedFile] = useState<FileType | null>(null);
|
||||||
|
const [showPreview, setShowPreview] = useState(false);
|
||||||
|
|
||||||
|
const fileArray = Array.isArray(files) ? files : [files];
|
||||||
|
|
||||||
|
// Helper function to check if file type is previewable
|
||||||
|
const isPreviewableType = (fileType: string): boolean => {
|
||||||
|
return (
|
||||||
|
fileType.startsWith('image/') ||
|
||||||
|
fileType.startsWith('video/') ||
|
||||||
|
fileType.startsWith('audio/') ||
|
||||||
|
fileType === 'application/pdf' ||
|
||||||
|
fileType.startsWith('text/') ||
|
||||||
|
fileType === 'application/json'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen) {
|
||||||
|
// Only set loading if the file is previewable
|
||||||
|
if (selectedFile && isPreviewableType(selectedFile.type)) {
|
||||||
|
setLoading(true);
|
||||||
|
} else {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
setError(null);
|
||||||
|
// If single file, show preview directly
|
||||||
|
if (!Array.isArray(files)) {
|
||||||
|
setSelectedFile(files);
|
||||||
|
setShowPreview(true);
|
||||||
|
} else {
|
||||||
|
setShowPreview(false);
|
||||||
|
setSelectedFile(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [isOpen, files]);
|
||||||
|
|
||||||
|
const handleLoadSuccess = () => {
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLoadError = () => {
|
||||||
|
setLoading(false);
|
||||||
|
setError('Failed to load file');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFileSelect = (file: FileType) => {
|
||||||
|
setSelectedFile(file);
|
||||||
|
setShowPreview(true);
|
||||||
|
// Only set loading if the file is previewable
|
||||||
|
setLoading(isPreviewableType(file.type));
|
||||||
|
setError(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBackToList = () => {
|
||||||
|
setShowPreview(false);
|
||||||
|
setSelectedFile(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderFileContent = (file: FileType) => {
|
||||||
|
const fileType = file.type.toLowerCase();
|
||||||
|
|
||||||
|
// If not a previewable type, don't show loading state
|
||||||
|
if (!isPreviewableType(fileType)) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center p-8">
|
||||||
|
<div className="text-4xl mb-4">📄</div>
|
||||||
|
<p className="text-center">
|
||||||
|
This file type ({file.type}) cannot be previewed.
|
||||||
|
<br />
|
||||||
|
<a
|
||||||
|
href={file.url}
|
||||||
|
download={file.name}
|
||||||
|
className="btn btn-primary mt-4"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
Open in New Tab
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileType.startsWith('image/')) {
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
src={file.url}
|
||||||
|
alt={file.name}
|
||||||
|
className="max-w-full max-h-[70vh] object-contain"
|
||||||
|
onLoad={handleLoadSuccess}
|
||||||
|
onError={handleLoadError}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileType.startsWith('video/')) {
|
||||||
|
return (
|
||||||
|
<video
|
||||||
|
controls
|
||||||
|
className="max-w-full max-h-[70vh]"
|
||||||
|
onLoadedData={handleLoadSuccess}
|
||||||
|
onError={handleLoadError}
|
||||||
|
>
|
||||||
|
<source src={file.url} type={file.type} />
|
||||||
|
Your browser does not support the video tag.
|
||||||
|
</video>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileType === 'application/pdf') {
|
||||||
|
return (
|
||||||
|
<iframe
|
||||||
|
src={file.url}
|
||||||
|
className="w-full h-[70vh]"
|
||||||
|
onLoad={handleLoadSuccess}
|
||||||
|
onError={handleLoadError}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileType.startsWith('text/') || fileType === 'application/json') {
|
||||||
|
return (
|
||||||
|
<iframe
|
||||||
|
src={file.url}
|
||||||
|
className="w-full h-[70vh] font-mono"
|
||||||
|
onLoad={handleLoadSuccess}
|
||||||
|
onError={handleLoadError}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileType.startsWith('audio/')) {
|
||||||
|
return (
|
||||||
|
<audio
|
||||||
|
controls
|
||||||
|
className="w-full"
|
||||||
|
onLoadedData={handleLoadSuccess}
|
||||||
|
onError={handleLoadError}
|
||||||
|
>
|
||||||
|
<source src={file.url} type={file.type} />
|
||||||
|
Your browser does not support the audio element.
|
||||||
|
</audio>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback for unsupported file types
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center p-8">
|
||||||
|
<div className="text-4xl mb-4">📄</div>
|
||||||
|
<p className="text-center">
|
||||||
|
This file type ({file.type}) cannot be previewed.
|
||||||
|
<br />
|
||||||
|
<a
|
||||||
|
href={file.url}
|
||||||
|
download={file.name}
|
||||||
|
className="btn btn-primary mt-4"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
Open in New Tab
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderFileList = () => {
|
||||||
|
return (
|
||||||
|
<div className="w-full">
|
||||||
|
<h3 className="font-bold text-lg mb-4">Files ({fileArray.length})</h3>
|
||||||
|
<div className="overflow-y-auto max-h-[60vh]">
|
||||||
|
{fileArray.map((file, index) => (
|
||||||
|
<div
|
||||||
|
key={`${file.name}-${index}`}
|
||||||
|
className="flex items-center justify-between p-4 hover:bg-base-200 rounded-lg cursor-pointer mb-2"
|
||||||
|
onClick={() => handleFileSelect(file)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="text-2xl">
|
||||||
|
{file.type.startsWith('image/') ? '🖼️' :
|
||||||
|
file.type.startsWith('video/') ? '🎥' :
|
||||||
|
file.type.startsWith('audio/') ? '🎵' :
|
||||||
|
file.type === 'application/pdf' ? '📄' :
|
||||||
|
file.type.startsWith('text/') ? '📝' : '📎'}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="font-semibold">{file.name}</div>
|
||||||
|
<div className="text-sm opacity-70">{file.type}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button className="btn btn-ghost btn-sm">
|
||||||
|
Preview
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clone the modal with a new ID
|
||||||
|
const cloneModal = () => {
|
||||||
|
const newModalId = `${modalId}-${Date.now()}`;
|
||||||
|
return (
|
||||||
|
<FileViewerModal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={onClose}
|
||||||
|
files={files}
|
||||||
|
modalId={newModalId}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id={modalId}
|
||||||
|
className="modal-toggle"
|
||||||
|
checked={isOpen}
|
||||||
|
onChange={onClose}
|
||||||
|
/>
|
||||||
|
<div className="modal">
|
||||||
|
<div className="modal-box max-w-4xl">
|
||||||
|
<div className="flex justify-between items-center mb-4">
|
||||||
|
{showPreview && selectedFile ? (
|
||||||
|
<>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<button
|
||||||
|
className="btn btn-ghost btn-sm"
|
||||||
|
onClick={handleBackToList}
|
||||||
|
style={{ display: Array.isArray(files) ? 'block' : 'none' }}
|
||||||
|
>
|
||||||
|
← Back
|
||||||
|
</button>
|
||||||
|
<h3 className="font-bold text-lg truncate">{selectedFile.name}</h3>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<h3 className="font-bold text-lg">File Browser</h3>
|
||||||
|
)}
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button
|
||||||
|
className="btn btn-circle btn-ghost"
|
||||||
|
onClick={cloneModal}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="h-6 w-6"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn btn-circle btn-ghost"
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="h-6 w-6"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M6 18L18 6M6 6l12 12"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative">
|
||||||
|
{loading && showPreview && (
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center bg-base-200 bg-opacity-50">
|
||||||
|
<span className="loading loading-spinner loading-lg"></span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{error ? (
|
||||||
|
<div className="alert alert-error">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="stroke-current shrink-0 h-6 w-6"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span>{error}</span>
|
||||||
|
</div>
|
||||||
|
) : showPreview && selectedFile ? (
|
||||||
|
renderFileContent(selectedFile)
|
||||||
|
) : (
|
||||||
|
renderFileList()
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<label className="modal-backdrop" htmlFor={modalId}>
|
||||||
|
Close
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FileViewerModalWrapper;
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
// Import the FilePreviewModal component
|
// Import the FileViewerModal component
|
||||||
import FilePreviewModal from "../modals/FilePreviewModal.astro";
|
import FileViewerModal from "../modals/FileViewerModal";
|
||||||
|
|
||||||
// Define the component's props and setup
|
// Define the component's props and setup
|
||||||
interface Props {}
|
interface Props {}
|
||||||
|
@ -124,21 +124,14 @@ const {} = Astro.props;
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Add the FilePreviewModal component -->
|
<!-- Add the FileViewerModal component -->
|
||||||
<FilePreviewModal id="eventFileViewer" title="Event Files" />
|
<FileViewerModal client:load />
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// TypeScript declarations
|
// TypeScript declarations
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
showEventFiles: (eventId: string) => Promise<void>;
|
showEventFiles: (eventId: string) => Promise<void>;
|
||||||
filePreviewModal: {
|
|
||||||
show: (
|
|
||||||
file:
|
|
||||||
| { url: string; name: string }
|
|
||||||
| Array<{ url: string; name: string }>
|
|
||||||
) => void;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,6 +145,26 @@ const {} = Astro.props;
|
||||||
const logger = SendLog.getInstance();
|
const logger = SendLog.getInstance();
|
||||||
const fileManager = FileManager.getInstance();
|
const fileManager = FileManager.getInstance();
|
||||||
|
|
||||||
|
// Initialize the modal state
|
||||||
|
window.fileViewerModal = {
|
||||||
|
isOpen: false,
|
||||||
|
files: [],
|
||||||
|
setIsOpen: (isOpen: boolean) => {
|
||||||
|
window.fileViewerModal.isOpen = isOpen;
|
||||||
|
const modal = document.querySelector("file-viewer-modal") as any;
|
||||||
|
if (modal) {
|
||||||
|
modal.setAttribute("isOpen", isOpen.toString());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setFiles: (files) => {
|
||||||
|
window.fileViewerModal.files = files;
|
||||||
|
const modal = document.querySelector("file-viewer-modal") as any;
|
||||||
|
if (modal) {
|
||||||
|
modal.setAttribute("files", JSON.stringify(files));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// Track if we're currently fetching events
|
// Track if we're currently fetching events
|
||||||
let isFetchingEvents = false;
|
let isFetchingEvents = false;
|
||||||
let lastFetchPromise: Promise<void> | null = null;
|
let lastFetchPromise: Promise<void> | null = null;
|
||||||
|
@ -521,20 +534,9 @@ const {} = Astro.props;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add global function to handle showing event files
|
// Update the showEventFiles function to use custom events
|
||||||
window.showEventFiles = async (eventId: string) => {
|
window.showEventFiles = async (eventId: string) => {
|
||||||
try {
|
try {
|
||||||
const modal = document.getElementById(
|
|
||||||
"eventFileViewer"
|
|
||||||
) as HTMLDialogElement;
|
|
||||||
if (!modal) {
|
|
||||||
console.error("Modal element not found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show the modal first
|
|
||||||
modal.showModal();
|
|
||||||
|
|
||||||
// Fetch the event data
|
// Fetch the event data
|
||||||
const event = await get.getOne("events", eventId);
|
const event = await get.getOne("events", eventId);
|
||||||
|
|
||||||
|
@ -543,150 +545,60 @@ const {} = Astro.props;
|
||||||
!Array.isArray(event.files) ||
|
!Array.isArray(event.files) ||
|
||||||
event.files.length === 0
|
event.files.length === 0
|
||||||
) {
|
) {
|
||||||
const previewContainer = document.getElementById(
|
console.warn("No files available for event:", eventId);
|
||||||
"eventFileViewer-preview-container"
|
|
||||||
);
|
|
||||||
if (previewContainer) {
|
|
||||||
previewContainer.innerHTML = `
|
|
||||||
<div class="flex items-center justify-center h-full">
|
|
||||||
<div class="text-center text-base-content/70">
|
|
||||||
<p>No files available</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert files to the format expected by FilePreviewModal
|
// Convert files to the format expected by FileViewerModal
|
||||||
const files = event.files.map((file) => {
|
const files = event.files.map((file: string) => {
|
||||||
const fileName = file.split("/").pop() || "File";
|
const fileName = file.split("/").pop() || "File";
|
||||||
|
const fileUrl = fileManager.getFileUrl(
|
||||||
|
"events",
|
||||||
|
event.id,
|
||||||
|
file
|
||||||
|
);
|
||||||
|
const fileType = getFileType(fileName);
|
||||||
return {
|
return {
|
||||||
url: fileManager.getFileUrl("events", event.id, file),
|
url: fileUrl,
|
||||||
|
type: fileType,
|
||||||
name: fileName,
|
name: fileName,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// If it's a single file, show it directly
|
// Dispatch custom event to show files
|
||||||
if (files.length === 1) {
|
const showFileViewerEvent = new CustomEvent("showFileViewer", {
|
||||||
const previewContainer = document.getElementById(
|
detail: { files },
|
||||||
"eventFileViewer-preview-container"
|
});
|
||||||
);
|
window.dispatchEvent(showFileViewerEvent);
|
||||||
const loadingElement = document.getElementById(
|
|
||||||
"eventFileViewer-loading"
|
|
||||||
);
|
|
||||||
const titleElement = document.getElementById(
|
|
||||||
"eventFileViewer-title"
|
|
||||||
);
|
|
||||||
const externalLink = document.getElementById(
|
|
||||||
"eventFileViewer-external-link"
|
|
||||||
) as HTMLAnchorElement;
|
|
||||||
|
|
||||||
if (
|
|
||||||
previewContainer &&
|
|
||||||
loadingElement &&
|
|
||||||
titleElement &&
|
|
||||||
externalLink
|
|
||||||
) {
|
|
||||||
// Show loading
|
|
||||||
loadingElement.classList.remove("hidden");
|
|
||||||
previewContainer.classList.add("hidden");
|
|
||||||
|
|
||||||
// Update title and external link
|
|
||||||
titleElement.textContent = files[0].name;
|
|
||||||
externalLink.href = files[0].url;
|
|
||||||
|
|
||||||
// Create preview element
|
|
||||||
const filePreview = document.createElement("div");
|
|
||||||
filePreview.className = "w-full h-full";
|
|
||||||
|
|
||||||
// Handle PDF files
|
|
||||||
if (files[0].name.toLowerCase().endsWith(".pdf")) {
|
|
||||||
filePreview.innerHTML = `
|
|
||||||
<embed
|
|
||||||
src="${files[0].url}#toolbar=0&navpanes=0"
|
|
||||||
type="application/pdf"
|
|
||||||
class="w-full h-full"
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
} else {
|
|
||||||
// For other files, show download option
|
|
||||||
filePreview.innerHTML = `
|
|
||||||
<div class="flex items-center justify-center h-full">
|
|
||||||
<div class="text-center">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 mx-auto mb-4 text-base-content/50" viewBox="0 0 20 20" fill="currentColor">
|
|
||||||
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z" clip-rule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
<p class="mb-4">${files[0].name}</p>
|
|
||||||
<a href="${files[0].url}" download class="btn btn-primary btn-sm">
|
|
||||||
Download File
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear previous content and add new preview
|
|
||||||
previewContainer.innerHTML = "";
|
|
||||||
previewContainer.appendChild(filePreview);
|
|
||||||
|
|
||||||
// Hide loading after a short delay
|
|
||||||
setTimeout(() => {
|
|
||||||
loadingElement.classList.add("hidden");
|
|
||||||
previewContainer.classList.remove("hidden");
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// For multiple files, show the grid view
|
|
||||||
const fileGrid = document.getElementById(
|
|
||||||
"eventFileViewer-file-grid"
|
|
||||||
);
|
|
||||||
const multipleView = document.getElementById(
|
|
||||||
"eventFileViewer-multiple"
|
|
||||||
);
|
|
||||||
const singleView = document.getElementById(
|
|
||||||
"eventFileViewer-single"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (fileGrid && multipleView && singleView) {
|
|
||||||
singleView.classList.add("hidden");
|
|
||||||
multipleView.classList.remove("hidden");
|
|
||||||
|
|
||||||
fileGrid.innerHTML = files
|
|
||||||
.map(
|
|
||||||
(file) => `
|
|
||||||
<div class="card bg-base-200 hover:bg-base-300 cursor-pointer transition-colors">
|
|
||||||
<div class="aspect-video bg-base-300 rounded-t-lg flex items-center justify-center">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 opacity-50" viewBox="0 0 20 20" fill="currentColor">
|
|
||||||
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z" clip-rule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="card-body p-4">
|
|
||||||
<h3 class="card-title text-sm" title="${file.name}">
|
|
||||||
<span class="truncate">${file.name}</span>
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
)
|
|
||||||
.join("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to load event files:", err);
|
console.error("Failed to load event files:", err);
|
||||||
const previewContainer = document.getElementById(
|
await logger.send(
|
||||||
"eventFileViewer-preview-container"
|
"error",
|
||||||
|
"show event files",
|
||||||
|
`Failed to load event files: ${err instanceof Error ? err.message : "Unknown error"}`
|
||||||
);
|
);
|
||||||
if (previewContainer) {
|
|
||||||
previewContainer.innerHTML = `
|
|
||||||
<div class="flex items-center justify-center h-full">
|
|
||||||
<div class="text-center text-error">
|
|
||||||
<p>Failed to load files</p>
|
|
||||||
<p class="text-sm mt-2 opacity-70">${err instanceof Error ? err.message : "Unknown error"}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Helper function to determine file type
|
||||||
|
function getFileType(fileName: string): string {
|
||||||
|
const extension = fileName.split(".").pop()?.toLowerCase() || "";
|
||||||
|
const mimeTypes: Record<string, string> = {
|
||||||
|
pdf: "application/pdf",
|
||||||
|
jpg: "image/jpeg",
|
||||||
|
jpeg: "image/jpeg",
|
||||||
|
png: "image/png",
|
||||||
|
gif: "image/gif",
|
||||||
|
mp4: "video/mp4",
|
||||||
|
webm: "video/webm",
|
||||||
|
mp3: "audio/mpeg",
|
||||||
|
wav: "audio/wav",
|
||||||
|
txt: "text/plain",
|
||||||
|
json: "application/json",
|
||||||
|
doc: "application/msword",
|
||||||
|
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||||
|
};
|
||||||
|
|
||||||
|
return mimeTypes[extension] || "application/octet-stream";
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
Loading…
Reference in a new issue