- Add i18n (English + 简体中文) with a locale store, $t() magic, and a language toggle (persisted) - Add light/dark/system theme switching via a theme store and [data-theme] token overrides - Set the theme before paint to avoid a flash of the wrong theme - Add frontend version constant web/version.js (window.YDROPBX_VERSION) shown in the app footer - Update AGENTS.md version-control convention to mention web/version.js - Bump version to 0.1.1 across frontend, webui changelog, and backend constant
89 lines
2.6 KiB
JavaScript
89 lines
2.6 KiB
JavaScript
/**
|
|
* Global state store (Alpine.store).
|
|
*
|
|
* Single source of truth for the file lists and shared actions, analogous to a
|
|
* Pinia/Redux store in a framework app. Components read reactive getters and
|
|
* dispatch actions here instead of owning their own duplicate state.
|
|
*/
|
|
(function () {
|
|
function register() {
|
|
Alpine.store('files', {
|
|
files: { inbox: [], outbox: [] },
|
|
toast: { show: false, message: '', _timer: null },
|
|
|
|
async init() {
|
|
await this.loadFiles();
|
|
},
|
|
|
|
async loadFiles() {
|
|
try {
|
|
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.files.inbox = inbox.files || [];
|
|
this.files.outbox = outbox.files || [];
|
|
} catch (e) {
|
|
this.showToast('toast.loadFailed');
|
|
}
|
|
},
|
|
|
|
showToast(message) {
|
|
this.toast.message = Alpine.store('locale').t(message);
|
|
this.toast.show = true;
|
|
if (this.toast._timer) clearTimeout(this.toast._timer);
|
|
this.toast._timer = setTimeout(() => {
|
|
this.toast.show = false;
|
|
}, 2500);
|
|
},
|
|
|
|
async upload(dir, fileList) {
|
|
if (!fileList || !fileList.length) return;
|
|
for (const file of fileList) {
|
|
const form = new FormData();
|
|
form.append('file', file);
|
|
form.append('dir', dir);
|
|
await fetch('/api/upload', { method: 'POST', body: form });
|
|
}
|
|
this.showToast('toast.uploaded');
|
|
await this.loadFiles();
|
|
},
|
|
|
|
download(id) {
|
|
window.location.href = '/api/files/' + id + '/download';
|
|
},
|
|
|
|
async deleteFile(id) {
|
|
const confirmed = await Alpine.store('confirm').show({
|
|
title: 'confirm.deleteTitle',
|
|
message: 'confirm.deleteMessage',
|
|
danger: true,
|
|
});
|
|
if (!confirmed) return;
|
|
await fetch('/api/files/' + id, { method: 'DELETE' });
|
|
this.showToast('toast.deleted');
|
|
await this.loadFiles();
|
|
},
|
|
|
|
async share(id) {
|
|
const res = await fetch('/api/files/' + id + '/share', { method: 'POST' });
|
|
const data = await res.json();
|
|
try {
|
|
await navigator.clipboard.writeText(data.url);
|
|
this.showToast('toast.shareCopied');
|
|
} catch (_) {
|
|
window.prompt('Share URL:', data.url);
|
|
}
|
|
},
|
|
|
|
async logout() {
|
|
await fetch('/api/logout', { method: 'POST' });
|
|
window.location.href = '/login';
|
|
},
|
|
});
|
|
}
|
|
|
|
if (window.Alpine) register();
|
|
else document.addEventListener('alpine:init', register);
|
|
})();
|