2 Commits

Author SHA1 Message Date
Toni
fa0c998d0f upload progress bar 2026-02-09 16:27:58 +01:00
Toni
3fc28af508 Refactor UI auth flow, remove uploads app files
Rework client UI state: move and update showLogin/showApp in public/app.js to explicitly set display styles when toggling screens and ensure user info and file load on app show. Remove the theme toggle from public/index.html and simplify CSS by collapsing color variable definitions (dark-theme-focused) in public/style.css. Add a VS Code workspace file. Remove the old uploads/Think app (Dockerfile, package.json, server.js and associated lockfile).
2026-02-09 11:08:59 +01:00
8 changed files with 141 additions and 2303 deletions

View File

@@ -0,0 +1,8 @@
{
"folders": [
{
"path": "."
}
],
"settings": {}
}

View File

@@ -65,18 +65,7 @@ document.getElementById('logout-btn').addEventListener('click', async () => {
window.location.reload();
});
function showLogin() {
loginScreen.classList.remove('hidden');
appScreen.classList.add('hidden');
}
function showApp() {
loginScreen.classList.add('hidden');
appScreen.classList.remove('hidden');
document.getElementById('user-display').textContent = currentUser.username;
loadFiles();
updateViewIcon();
}
// --- View Mode ---
function toggleView() {
@@ -94,6 +83,27 @@ function updateViewIcon() {
}
viewToggleBtn.addEventListener('click', toggleView);
// --- Auth / UI State ---
function showLogin() {
// Force style to ensure hidden/visible
loginScreen.classList.remove('hidden');
loginScreen.style.display = 'flex';
appScreen.classList.add('hidden');
}
function showApp() {
loginScreen.classList.add('hidden');
loginScreen.style.display = 'none'; // Force hide
appScreen.classList.remove('hidden');
document.getElementById('user-display').textContent = currentUser.username;
loadFiles();
updateViewIcon();
}
// --- Files ---
async function loadFiles(path = currentPath) {
try {
@@ -375,22 +385,87 @@ dropZone.addEventListener('drop', (e) => {
}
});
// File Input Change
document.getElementById('file-input').addEventListener('change', (e) => {
if (e.target.files.length > 0) {
uploadFiles(e.target.files);
// Reset input so same file can be selected again if needed
e.target.value = '';
}
});
async function uploadFiles(files) {
const statusDiv = document.getElementById('upload-status');
const progressContainer = document.getElementById('progress-container');
const progressBar = document.getElementById('progress-bar');
const progressText = document.getElementById('progress-text');
statusDiv.innerHTML = 'Uploading...';
progressContainer.classList.remove('hidden');
progressBar.style.width = '0%';
progressText.textContent = '0%';
let totalSize = 0;
for (let file of files) totalSize += file.size;
// Avoid division by zero
if (totalSize === 0) totalSize = 1;
let previousFilesSize = 0;
for (let file of files) {
const formData = new FormData();
formData.append('file', file);
if (currentPath) {
await fetch(`/api/upload?path=${encodeURIComponent(currentPath)}`, { method: 'POST', body: formData });
} else {
await fetch('/api/upload', { method: 'POST', body: formData });
try {
await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append('file', file);
const url = currentPath
? `/api/upload?path=${encodeURIComponent(currentPath)}`
: '/api/upload';
xhr.open('POST', url);
xhr.upload.onprogress = (e) => {
if (e.lengthComputable) {
const currentLoaded = e.loaded;
const totalLoaded = previousFilesSize + currentLoaded;
const percent = Math.min(100, Math.round((totalLoaded / totalSize) * 100));
progressBar.style.width = percent + '%';
progressText.textContent = percent + '%';
}
};
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve();
} else {
reject(new Error(`Upload failed: ${xhr.statusText}`));
}
};
xhr.onerror = () => reject(new Error('Network Error'));
xhr.send(formData);
});
previousFilesSize += file.size;
} catch (e) {
console.error(e);
statusDiv.innerHTML = `<span style="color:var(--danger-color)">Error uploading ${file.name}</span>`;
}
}
progressBar.style.width = '100%';
progressText.textContent = '100%';
statusDiv.innerHTML = 'Done!';
setTimeout(() => statusDiv.innerHTML = '', 2000);
setTimeout(() => {
statusDiv.innerHTML = '';
progressContainer.classList.add('hidden');
progressBar.style.width = '0%';
progressText.textContent = '0%';
}, 2000);
loadFiles();
}

View File

@@ -40,9 +40,7 @@
</div>
<div class="controls">
<span id="user-display"></span>
<button id="theme-toggle" class="btn secondary" aria-label="Toggle Dark Mode">
<span class="icon">🌓</span>
</button>
<button id="logout-btn" class="btn secondary">Logout</button>
</div>
</header>
@@ -69,6 +67,10 @@
Files</button>
</div>
<div id="upload-status"></div>
<div id="progress-container" class="hidden">
<div id="progress-bar"></div>
<div id="progress-text">0%</div>
</div>
<!-- File List -->
<section class="files-section">

View File

@@ -1,17 +1,4 @@
:root {
--bg-color: #f8f9fa;
--card-bg: #ffffff;
--text-color: #1f2937;
--text-secondary: #6b7280;
--primary-color: #3b82f6;
--primary-hover: #2563eb;
--border-color: #e5e7eb;
--danger-color: #ef4444;
--modal-bg: rgba(0, 0, 0, 0.8);
}
/* Dark Mode Variables */
[data-theme="dark"] {
--bg-color: #111827;
--card-bg: #1f2937;
--text-color: #f9fafb;
@@ -19,22 +6,10 @@
--primary-color: #60a5fa;
--primary-hover: #3b82f6;
--border-color: #374151;
--danger-color: #ef4444;
--modal-bg: rgba(0, 0, 0, 0.9);
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--bg-color: #111827;
--card-bg: #1f2937;
--text-color: #f9fafb;
--text-secondary: #9ca3af;
--primary-color: #60a5fa;
--primary-hover: #3b82f6;
--border-color: #374151;
--modal-bg: rgba(0, 0, 0, 0.9);
}
}
body {
font-family: 'Inter', sans-serif;
background-color: var(--bg-color);
@@ -271,6 +246,40 @@ header {
transform: scale(1.02);
}
/* Progress Bar */
#progress-container {
width: 100%;
background-color: var(--card-bg);
border-radius: 0.5rem;
margin-bottom: 1rem;
border: 1px solid var(--border-color);
position: relative;
height: 1.5rem;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
#progress-bar {
position: absolute;
left: 0;
top: 0;
height: 100%;
background-color: var(--primary-color);
width: 0%;
transition: width 0.2s;
}
#progress-text {
position: relative;
z-index: 1;
font-size: 0.8rem;
font-weight: 600;
color: white;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}
/* Modals */
.modal {
position: fixed;

View File

@@ -1,16 +0,0 @@
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
# Expose port 3000
EXPOSE 3000
# Create uploads directory
RUN mkdir -p uploads
CMD ["node", "server.js"]

File diff suppressed because it is too large Load Diff

View File

@@ -1,24 +0,0 @@
{
"name": "simple-file-server",
"version": "1.0.0",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"adm-zip": "^0.5.16",
"archiver": "^7.0.1",
"bcryptjs": "^3.0.3",
"cors": "^2.8.6",
"dotenv": "^17.2.4",
"express": "^5.2.1",
"express-session": "^1.19.0",
"mariadb": "^3.4.5",
"multer": "^2.0.2"
}
}

View File

@@ -1,51 +0,0 @@
const express = require('express');
const cors = require('cors');
const path = require('path');
const session = require('express-session');
require('dotenv').config();
const { initUserTable } = require('./db/connection');
const authRoutes = require('./routes/auth');
const fileRoutes = require('./routes/files');
const app = express();
const PORT = process.env.PORT || 3000;
// Initialize DB
initUserTable();
// Middleware
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Session
app.use(session({
secret: process.env.SESSION_SECRET || 'supersecretkey',
resave: false,
saveUninitialized: false,
cookie: { secure: false } // Set to true if using HTTPS
}));
// Auth Middleware
const requireAuth = (req, res, next) => {
if (req.session.user) {
next();
} else {
res.status(401).json({ error: 'Unauthorized' });
}
};
// Routes
app.use('/api/auth', authRoutes);
app.use('/api', requireAuth, fileRoutes);
// Static files (public) - protect if needed, but for now let's allow loading the app
// We can protect specific assets if we want, but the API is protected.
// Actually, if we want to force login, we can serve a login page or handle it in specific separate file.
// The main `index.html` handles the login UI, so it should be public.
app.use(express.static('public'));
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});