import { config } from "./config.ts"; import { formatFileSize, getMimeType, isViewableInBrowser } from "./fileUtils.ts"; const templateCache = new Map(); async function loadTemplate(path: string): Promise { if (templateCache.has(path)) { return templateCache.get(path)!; } const content = await Deno.readTextFile(path); templateCache.set(path, content); return content; } function replaceVariables(template: string, vars: Record): string { let result = template; for (const [key, value] of Object.entries(vars)) { result = result.replaceAll(`{{${key}}}`, value); } return result; } async function renderLayout(title: string, content: string, scripts = "", styles = ""): Promise { const layout = await loadTemplate("./templates/layout.html"); return replaceVariables(layout, { title, content, scripts, styles, }); } export async function renderUploadPage(chunked = false): Promise { const maxSizeMB = Math.floor(config.maxFileSize / (1024 * 1024)); const expirationDays = Math.floor(config.fileExpiration / (24 * 60 * 60 * 1000)); const uploadTemplate = await loadTemplate("./templates/upload.html"); const scriptTemplate = chunked ? await loadTemplate("./templates/scripts/chunked-upload.js") : await loadTemplate("./templates/scripts/simple-upload.js"); const scriptContent = replaceVariables(scriptTemplate, { "max-size": String(maxSizeMB), "chunk-size": String(config.chunkSize), }); const content = replaceVariables(uploadTemplate, { "section-suffix": chunked ? "-js" : "-nojs", "upload-type": chunked ? "(Chunked)" : "", "nav-links": chunked ? 'Simple Upload' : 'Chunked Upload', "form-attrs": chunked ? "" : 'action="/upload" method="POST" enctype="multipart/form-data"', "input-attrs": chunked ? "" : "required", "max-size": String(maxSizeMB), "expiration-days": String(expirationDays), "rate-limit-max": String(config.rateLimit.maxUploads), "rate-limit-minutes": String(config.rateLimit.windowMs / 60000), }); const scripts = ``; return renderLayout("File Upload", content, scripts); } export async function renderBrowsePage(files: Array<{ name: string; size: number; uploaded: Date }>): Promise { const browseTemplate = await loadTemplate("./templates/browse.html"); const fileRowTemplate = await loadTemplate("./templates/partials/file-row.html"); const emptyFilesTemplate = await loadTemplate("./templates/partials/empty-files.html"); const fileRows = files.map(file => { const mimeType = getMimeType(file.name); const viewable = isViewableInBrowser(mimeType); const encodedName = encodeURIComponent(file.name); const viewButton = viewable ? `View` : ''; return replaceVariables(fileRowTemplate, { "encoded-name": encodedName, "filename": file.name, "size": formatFileSize(file.size), "uploaded": file.uploaded.toLocaleString(), "view-button": viewButton, }); }).join(''); const filesContent = files.length > 0 ? ` ${fileRows}
Filename Size Uploaded Actions
` : emptyFilesTemplate; const content = replaceVariables(browseTemplate, { "files-content": filesContent, }); return renderLayout("Browse Files", content); } export async function renderGalleryPage(files: Array<{ name: string; size: number; uploaded: Date }>): Promise { const galleryTemplate = await loadTemplate("./templates/gallery.html"); const galleryItemTemplate = await loadTemplate("./templates/partials/gallery-item.html"); const emptyGalleryTemplate = await loadTemplate("./templates/partials/empty-gallery.html"); // Filter only images and videos const mediaFiles = files.filter(file => { const mimeType = getMimeType(file.name); return mimeType.startsWith("image/") || mimeType.startsWith("video/"); }); const galleryItems = mediaFiles.map((file, index) => { const mimeType = getMimeType(file.name); const isVideo = mimeType.startsWith("video/"); const encodedName = encodeURIComponent(file.name); const media = isVideo ? `
` : `${file.name}`; return replaceVariables(galleryItemTemplate, { "index": String(index), "media": media, "filename": file.name, "size": formatFileSize(file.size), "uploaded": file.uploaded.toLocaleDateString(), }); }).join(''); const galleryContent = mediaFiles.length > 0 ? `` : emptyGalleryTemplate; const content = replaceVariables(galleryTemplate, { "gallery-content": galleryContent, }); const mediaFilesJson = JSON.stringify(mediaFiles.map(file => ({ name: file.name, isVideo: getMimeType(file.name).startsWith("video/"), size: formatFileSize(file.size), uploaded: file.uploaded.toLocaleDateString() }))); const slideshowScript = await loadTemplate("./templates/scripts/slideshow.js"); const scriptContent = replaceVariables(slideshowScript, { "media-files-json": mediaFilesJson, }); const styles = ''; const scripts = ``; return renderLayout("Gallery", content, scripts, styles); } export async function renderError(error: string, status = 400): Promise { const errorTemplate = await loadTemplate("./templates/error.html"); const content = replaceVariables(errorTemplate, { status: String(status), "error-message": error, }); return renderLayout("Error", content); }