feat: web frontend with Alpine.js

This commit is contained in:
2026-08-26 23:36:27 +08:00
parent c59654fcaa
commit c24df57882
5 changed files with 121 additions and 5 deletions
+5 -1
View File
File diff suppressed because one or more lines are too long
+38 -1
View File
@@ -1 +1,38 @@
// placeholder function app() {
return {
inboxFiles: [],
outboxFiles: [],
async loadFiles() {
const [inbox, outbox] = await Promise.all([
fetch('/api/files?dir=inbox').then(r => r.json()),
fetch('/api/files?dir=outbox').then(r => r.json())
]);
this.inboxFiles = inbox.files || [];
this.outboxFiles = outbox.files || [];
},
async upload(event, dir) {
const file = event.target.files[0];
const form = new FormData();
form.append('file', file);
form.append('dir', dir);
await fetch('/api/upload', {method: 'POST', body: form});
this.loadFiles();
},
download(id) {
window.location.href = '/api/files/' + id + '/download';
},
async deleteFile(id) {
await fetch('/api/files/' + id, {method: 'DELETE'});
this.loadFiles();
},
async share(id) {
const res = await fetch('/api/files/' + id + '/share', {method: 'POST'});
const data = await res.json();
alert('Share URL: ' + data.url);
},
async logout() {
await fetch('/api/logout', {method: 'POST'});
window.location.href = '/login';
}
};
}
+41 -1
View File
@@ -1,2 +1,42 @@
<!DOCTYPE html> <!DOCTYPE html>
<html><head><title>yDropbox</title></head><body><h1>yDropbox</h1></body></html> <html>
<head>
<meta charset="UTF-8">
<title>yDropbox</title>
<link rel="stylesheet" href="/style.css">
<script defer src="/alpine.min.js"></script>
</head>
<body>
<div x-data="app()" x-init="loadFiles()">
<header>
<h1>yDropbox</h1>
<button @click="logout()">Logout</button>
</header>
<section>
<h2>Inbox</h2>
<input type="file" @change="upload($event, 'inbox')">
<template x-for="file in inboxFiles" :key="file.id">
<div class="file-row">
<span x-text="file.original_name"></span>
<button @click="download(file.id)">Download</button>
<button @click="deleteFile(file.id)">Delete</button>
<button @click="share(file.id)">Share</button>
</div>
</template>
</section>
<section>
<h2>Outbox</h2>
<template x-for="file in outboxFiles" :key="file.id">
<div class="file-row">
<span x-text="file.original_name"></span>
<button @click="download(file.id)">Download</button>
<button @click="deleteFile(file.id)">Delete</button>
</div>
</template>
</section>
</div>
<script src="/app.js"></script>
</body>
</html>
+29 -1
View File
@@ -1,2 +1,30 @@
<!DOCTYPE html> <!DOCTYPE html>
<html><head><title>Login</title></head><body><h1>Login</h1></body></html> <html>
<head>
<meta charset="UTF-8">
<title>yDropbox Login</title>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<div class="container">
<h1>yDropbox</h1>
<form id="loginForm">
<input type="password" id="token" placeholder="Access Token" required>
<button type="submit">Login</button>
</form>
</div>
<script>
document.getElementById('loginForm').addEventListener('submit', async (e) => {
e.preventDefault();
const token = document.getElementById('token').value;
const res = await fetch('/api/login', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({token})
});
if (res.ok) window.location.href = '/';
else alert('Invalid token');
});
</script>
</body>
</html>
+8 -1
View File
@@ -1 +1,8 @@
/* placeholder */ body { font-family: sans-serif; max-width: 800px; margin: 40px auto; padding: 0 20px; }
header { display: flex; justify-content: space-between; align-items: center; }
section { margin: 20px 0; }
.file-row { display: flex; gap: 10px; align-items: center; margin: 10px 0; }
button { cursor: pointer; }
.container { max-width: 400px; margin: 100px auto; }
form { display: flex; flex-direction: column; gap: 10px; }
input[type="password"] { padding: 8px; font-size: 16px; }