feat(ui): add file-type icons and clamp window titles

Add a file-to-icon resolver for common MIME types/extensions so uploads
display appropriate Win98-style icons. Update upload and window CSS to
use image-based, pixelated icons, and prevent long window titles from
overflowing by adding a flex label with ellipsis handling.feat(ui): add file-type icons and clamp window titles

Add a file-to-icon resolver for common MIME types/extensions so uploads
display appropriate Win98-style icons. Update upload and window CSS to
use image-based, pixelated icons, and prevent long window titles from
overflowing by adding a flex label with ellipsis handling.
This commit is contained in:
2026-04-27 18:37:05 +03:00
parent 041a9798a7
commit c1489d1fbb
6 changed files with 142 additions and 75 deletions

View File

@@ -34,6 +34,59 @@
border: 1px dotted #000000; border: 1px dotted #000000;
} }
.upload-dropzone.is-dragging {
background: #c7d8f2;
outline: 2px solid #000078;
outline-offset: -4px;
}
.upload-dropzone:focus-visible {
outline: 1px dotted #000000;
outline-offset: -5px;
}
.upload-icon {
width: 34px;
height: 30px;
position: relative;
box-sizing: border-box;
background: #ffffff;
border: 2px solid #000000;
box-shadow: inset -3px -3px 0 #dfdfdf;
}
.upload-icon::before {
content: "";
position: absolute;
right: -2px;
top: -2px;
width: 10px;
height: 10px;
box-sizing: border-box;
background: #dfdfdf;
border-left: 2px solid #000000;
border-bottom: 2px solid #000000;
}
.upload-primary {
font-size: 18px;
line-height: 18px;
font-weight: bold;
}
.upload-secondary {
font-size: 13px;
line-height: 15px;
}
.upload-input {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
}
.upload-options { .upload-options {
flex: 0 0 auto; flex: 0 0 auto;
display: grid; display: grid;
@@ -104,59 +157,6 @@
background: #c0c0c0; background: #c0c0c0;
} }
.upload-dropzone.is-dragging {
background: #c7d8f2;
outline: 2px solid #000078;
outline-offset: -4px;
}
.upload-dropzone:focus-visible {
outline: 1px dotted #000000;
outline-offset: -5px;
}
.upload-icon {
width: 34px;
height: 30px;
position: relative;
box-sizing: border-box;
background: #ffffff;
border: 2px solid #000000;
box-shadow: inset -3px -3px 0 #dfdfdf;
}
.upload-icon::before {
content: "";
position: absolute;
right: -2px;
top: -2px;
width: 10px;
height: 10px;
box-sizing: border-box;
background: #dfdfdf;
border-left: 2px solid #000000;
border-bottom: 2px solid #000000;
}
.upload-primary {
font-size: 18px;
line-height: 18px;
font-weight: bold;
}
.upload-secondary {
font-size: 13px;
line-height: 15px;
}
.upload-input {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
}
.upload-details { .upload-details {
flex: 0 0 auto; flex: 0 0 auto;
display: flex; display: flex;
@@ -280,25 +280,10 @@
.upload-file-icon { .upload-file-icon {
grid-row: 1 / 3; grid-row: 1 / 3;
width: 16px; width: 18px;
height: 18px; height: 18px;
position: relative; object-fit: contain;
box-sizing: border-box; image-rendering: pixelated;
background: #ffffff;
border: 1px solid #000000;
box-shadow: inset -2px -2px 0 #dfdfdf;
}
.upload-file-icon::before {
content: "";
position: absolute;
top: -1px;
right: -1px;
width: 5px;
height: 5px;
background: #dfdfdf;
border-left: 1px solid #000000;
border-bottom: 1px solid #000000;
} }
.upload-file-name, .upload-file-name,

View File

@@ -26,14 +26,34 @@
} }
.win98-titlebar h1 { .win98-titlebar h1 {
min-width: 0;
margin: 0; margin: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 14px; font-size: 14px;
line-height: 14px; line-height: 14px;
font-weight: bold; font-weight: bold;
} }
.win98-titlebar-label {
display: flex;
align-items: center;
min-width: 0;
gap: 5px;
}
.win98-titlebar-icon {
flex: 0 0 auto;
width: 16px;
height: 16px;
object-fit: contain;
image-rendering: pixelated;
}
.win98-window-controls { .win98-window-controls {
display: flex; display: flex;
flex: 0 0 auto;
gap: 2px; gap: 2px;
} }

View File

@@ -36,6 +36,50 @@ function formatBytes(bytes) {
return `${size.toFixed(1)} ${units[unitIndex]}`; return `${size.toFixed(1)} ${units[unitIndex]}`;
} }
function iconForFile(file) {
const filename = file.name || "";
const mimeType = file.type || "";
const extension = filename.includes(".") ? filename.slice(filename.lastIndexOf(".")).toLowerCase() : "";
if (extension === ".exe") {
return "/static/img/icons/Program Files Icons - PNG/MSONSEXT.DLL_14_6-0.png";
}
if (mimeType.startsWith("image/")) {
return "/static/img/sprites/bitmap.png";
}
if (mimeType.startsWith("video/") || mimeType.startsWith("audio/")) {
return "/static/img/icons/netshow_notransm-1.png";
}
if (mimeType.startsWith("text/") || extension === ".md") {
return "/static/img/sprites/notepad_file-1.png";
}
if (
mimeType.includes("zip") ||
mimeType.includes("compressed") ||
[".rar", ".7z", ".tar", ".gz"].includes(extension)
) {
return "/static/img/icons/Windows Icons - PNG/zipfldr.dll_14_101-0.png";
}
if ([".ttf", ".otf", ".woff", ".woff2"].includes(extension)) {
return "/static/img/sprites/font.png";
}
if (extension === ".pdf") {
return "/static/img/sprites/journal.png";
}
if ([".html", ".css", ".js"].includes(extension)) {
return "/static/img/sprites/frame_web-0.png";
}
return "/static/img/icons/Windows Icons - PNG/ole2.dll_14_DEFICON.png";
}
function updateStatus(message) { function updateStatus(message) {
if (uploadStatus) { if (uploadStatus) {
uploadStatus.textContent = message; uploadStatus.textContent = message;
@@ -124,8 +168,10 @@ function createFileRow(selectedFile) {
const row = document.createElement("div"); const row = document.createElement("div");
row.className = "upload-file-row"; row.className = "upload-file-row";
const icon = document.createElement("span"); const icon = document.createElement("img");
icon.className = "upload-file-icon"; icon.className = "upload-file-icon";
icon.src = iconForFile(selectedFile.file);
icon.alt = "";
icon.setAttribute("aria-hidden", "true"); icon.setAttribute("aria-hidden", "true");
const name = document.createElement("span"); const name = document.createElement("span");
@@ -391,6 +437,10 @@ if (uploadForm) {
selectedFiles.forEach((selectedFile, index) => { selectedFiles.forEach((selectedFile, index) => {
selectedFile.boxID = box.box_id; selectedFile.boxID = box.box_id;
selectedFile.boxFile = box.files[index]; selectedFile.boxFile = box.files[index];
const icon = selectedFile.row.querySelector(".upload-file-icon");
if (icon && selectedFile.boxFile.icon_path) {
icon.src = selectedFile.boxFile.icon_path;
}
}); });
await Promise.allSettled(selectedFiles.map((selectedFile) => { await Promise.allSettled(selectedFiles.map((selectedFile) => {

View File

@@ -4,6 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WarpBox - {{ .BoxID }}</title> <title>WarpBox - {{ .BoxID }}</title>
<link rel="icon" type="image/png" href="/static/WarpBoxLogo.png">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/static/css/app.css"> <link rel="stylesheet" href="/static/css/app.css">
<link rel="stylesheet" href="/static/css/window.css"> <link rel="stylesheet" href="/static/css/window.css">
@@ -14,7 +15,10 @@
<main> <main>
<section class="win98-window box-window" aria-labelledby="box-window-title"> <section class="win98-window box-window" aria-labelledby="box-window-title">
<header class="win98-titlebar box-titlebar"> <header class="win98-titlebar box-titlebar">
<h1 id="box-window-title">WarpBox Explorer - {{ .BoxID }}</h1> <div class="win98-titlebar-label">
<img class="win98-titlebar-icon" src="/static/WarpBoxLogo.png" alt="" aria-hidden="true">
<h1 id="box-window-title">WarpBox Explorer - {{ .BoxID }}</h1>
</div>
<div class="win98-window-controls" aria-hidden="true"> <div class="win98-window-controls" aria-hidden="true">
<span class="win98-control">_</span> <span class="win98-control">_</span>
<span class="win98-control"></span> <span class="win98-control"></span>

View File

@@ -4,6 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WarpBox Login - {{ .BoxID }}</title> <title>WarpBox Login - {{ .BoxID }}</title>
<link rel="icon" type="image/png" href="/static/WarpBoxLogo.png">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/static/css/app.css"> <link rel="stylesheet" href="/static/css/app.css">
<link rel="stylesheet" href="/static/css/window.css"> <link rel="stylesheet" href="/static/css/window.css">
@@ -14,7 +15,10 @@
<main> <main>
<section class="win98-window login-window" aria-labelledby="login-window-title"> <section class="win98-window login-window" aria-labelledby="login-window-title">
<header class="win98-titlebar login-titlebar"> <header class="win98-titlebar login-titlebar">
<h1 id="login-window-title">Enter Network Password</h1> <div class="win98-titlebar-label">
<img class="win98-titlebar-icon" src="/static/WarpBoxLogo.png" alt="" aria-hidden="true">
<h1 id="login-window-title">Enter Network Password</h1>
</div>
<div class="win98-window-controls" aria-hidden="true"> <div class="win98-window-controls" aria-hidden="true">
<span class="win98-control">_</span> <span class="win98-control">_</span>
<span class="win98-control"></span> <span class="win98-control"></span>

View File

@@ -4,6 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Warpbox</title> <title>Warpbox</title>
<link rel="icon" type="image/png" href="/static/WarpBoxLogo.png">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/static/css/app.css"> <link rel="stylesheet" href="/static/css/app.css">
<link rel="stylesheet" href="/static/css/window.css"> <link rel="stylesheet" href="/static/css/window.css">
@@ -14,7 +15,10 @@
<main> <main>
<section class="win98-window upload-window" aria-labelledby="upload-window-title"> <section class="win98-window upload-window" aria-labelledby="upload-window-title">
<header class="win98-titlebar upload-titlebar"> <header class="win98-titlebar upload-titlebar">
<h1 id="upload-window-title">WarpBox Upload</h1> <div class="win98-titlebar-label">
<img class="win98-titlebar-icon" src="/static/WarpBoxLogo.png" alt="" aria-hidden="true">
<h1 id="upload-window-title">WarpBox Upload</h1>
</div>
<div class="win98-window-controls" aria-hidden="true"> <div class="win98-window-controls" aria-hidden="true">
<span class="win98-control">_</span> <span class="win98-control">_</span>
<span class="win98-control"></span> <span class="win98-control"></span>