uploads
This commit is contained in:
parent
ffb6fb074c
commit
7733cb3737
3
packages/attache/.gitignore
vendored
3
packages/attache/.gitignore
vendored
|
|
@ -1,3 +1,6 @@
|
|||
# DATA!
|
||||
projects/
|
||||
|
||||
# dependencies (bun install)
|
||||
node_modules
|
||||
|
||||
|
|
|
|||
|
|
@ -240,6 +240,15 @@ const Project = ({ project, files }: { project: Project, files: string[] }) => {
|
|||
</div>
|
||||
<div class="bg-white rounded-lg shadow-sm p-6">
|
||||
<h2 class="text-xl font-semibold mb-4">Files</h2>
|
||||
<div id="upload-progress" class="hidden mb-4">
|
||||
<div class="flex items-center gap-2 text-sm text-gray-600 mb-2">
|
||||
<span id="upload-filename"></span>
|
||||
<span id="upload-percent">0%</span>
|
||||
</div>
|
||||
<div class="w-full bg-gray-200 rounded-full h-2">
|
||||
<div id="upload-bar" class="bg-blue-500 h-2 rounded-full w-0 transition-all"></div>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="space-y-2">
|
||||
{files.map(file => (
|
||||
<li key={file} class="flex items-center justify-between py-2 px-4 hover:bg-gray-50 rounded">
|
||||
|
|
@ -289,6 +298,27 @@ const Project = ({ project, files }: { project: Project, files: string[] }) => {
|
|||
method: 'DELETE'
|
||||
}).then(() => location.href = '/projects')
|
||||
}
|
||||
|
||||
function showProgress(file) {
|
||||
const progress = document.getElementById("upload-progress")
|
||||
const filename = document.getElementById("upload-filename")
|
||||
const percent = document.getElementById("upload-percent")
|
||||
const bar = document.getElementById("upload-bar")
|
||||
|
||||
progress.classList.remove("hidden")
|
||||
filename.textContent = file.name
|
||||
percent.textContent = "0%"
|
||||
bar.style.width = "0%"
|
||||
}
|
||||
|
||||
function updateProgress(percent) {
|
||||
document.getElementById("upload-percent").textContent = percent + "%"
|
||||
document.getElementById("upload-bar").style.width = percent + "%"
|
||||
}
|
||||
|
||||
function hideProgress() {
|
||||
document.getElementById("upload-progress").classList.add("hidden")
|
||||
}
|
||||
</script>
|
||||
`}
|
||||
</div>
|
||||
|
|
@ -343,8 +373,30 @@ const Layout = ({ children, title }: { children: any, title: string }) => {
|
|||
<title>{title} - 💼 Attaché</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="icon" type="image/png" href="/public/favicon.png" />
|
||||
<style>{`
|
||||
.drag-active {
|
||||
position: relative;
|
||||
}
|
||||
.drag-active::after {
|
||||
content: "Drop files to upload";
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
backdrop-filter: blur(2px);
|
||||
z-index: 50;
|
||||
}
|
||||
`}</style>
|
||||
</head>
|
||||
<body class="min-h-screen bg-gray-50">
|
||||
<body
|
||||
class="min-h-screen bg-gray-50"
|
||||
ondragover="handleGlobalDragOver(event)"
|
||||
ondragleave="handleGlobalDragLeave(event)"
|
||||
ondrop="handleGlobalDrop(event)"
|
||||
>
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<nav class="mb-8">
|
||||
<div class="flex items-center justify-between">
|
||||
|
|
@ -356,6 +408,89 @@ const Layout = ({ children, title }: { children: any, title: string }) => {
|
|||
</nav>
|
||||
<main>{children}</main>
|
||||
</div>
|
||||
{html`
|
||||
<script>
|
||||
function handleGlobalDragOver(e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
if (window.location.pathname.startsWith("/project/")) {
|
||||
document.body.classList.add("drag-active")
|
||||
}
|
||||
}
|
||||
|
||||
function handleGlobalDragLeave(e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
const rect = document.body.getBoundingClientRect()
|
||||
if (
|
||||
e.clientX <= rect.left ||
|
||||
e.clientX >= rect.right ||
|
||||
e.clientY <= rect.top ||
|
||||
e.clientY >= rect.bottom
|
||||
) {
|
||||
document.body.classList.remove("drag-active")
|
||||
}
|
||||
}
|
||||
|
||||
function handleGlobalDrop(e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
document.body.classList.remove("drag-active")
|
||||
|
||||
if (!window.location.pathname.startsWith("/project/")) {
|
||||
return
|
||||
}
|
||||
|
||||
const files = e.dataTransfer.files
|
||||
const projectId = document.querySelector("[data-project]").dataset.project
|
||||
|
||||
let currentUpload = Promise.resolve()
|
||||
|
||||
for (const file of files) {
|
||||
currentUpload = currentUpload.then(async () => {
|
||||
const formData = new FormData()
|
||||
formData.append("file", file)
|
||||
|
||||
showProgress(file)
|
||||
|
||||
try {
|
||||
const xhr = new XMLHttpRequest()
|
||||
await new Promise((resolve, reject) => {
|
||||
xhr.upload.onprogress = (e) => {
|
||||
if (e.lengthComputable) {
|
||||
const percent = Math.round((e.loaded / e.total) * 100)
|
||||
updateProgress(percent)
|
||||
}
|
||||
}
|
||||
|
||||
xhr.onload = () => {
|
||||
if (xhr.status === 200) {
|
||||
resolve()
|
||||
} else {
|
||||
reject(new Error("Upload failed"))
|
||||
}
|
||||
}
|
||||
|
||||
xhr.onerror = () => reject(new Error("Upload failed"))
|
||||
|
||||
xhr.open("POST", "/api/project/" + projectId + "/upload")
|
||||
xhr.send(formData)
|
||||
})
|
||||
} catch (err) {
|
||||
console.error("Upload failed:", err)
|
||||
} finally {
|
||||
hideProgress()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
currentUpload.then(() => location.reload())
|
||||
}
|
||||
</script>
|
||||
`}
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
|
|
@ -363,5 +498,6 @@ const Layout = ({ children, title }: { children: any, title: string }) => {
|
|||
|
||||
export default {
|
||||
port: 3000,
|
||||
fetch: app.fetch
|
||||
fetch: app.fetch,
|
||||
maxRequestBodySize: 1024 * 1024 * 1024, // 1GB
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user