fix: web responsivity

Signed-off-by: skidoodle <contact@albert.lol>
This commit is contained in:
2026-01-16 04:03:38 +01:00
parent d7ba7f63c6
commit 956dff48eb
5 changed files with 193 additions and 42 deletions
+11 -9
View File
@@ -9,38 +9,34 @@ import (
func (app *App) Routes() *http.ServeMux { func (app *App) Routes() *http.ServeMux {
mux := http.NewServeMux() mux := http.NewServeMux()
fs := http.FileServer(http.Dir("./web/static")) fs := http.FileServer(http.Dir("./web/static"))
mux.Handle("GET /static/", http.StripPrefix("/static/", fs)) mux.Handle("GET /static/", http.StripPrefix("/static/", fs))
mux.HandleFunc("GET /{$}", app.HandleHome) mux.HandleFunc("GET /{$}", app.HandleHome)
mux.HandleFunc("POST /{$}", app.HandleUpload) mux.HandleFunc("POST /{$}", app.HandleUpload)
mux.HandleFunc("POST /upload/chunk", app.HandleChunk) mux.HandleFunc("POST /upload/chunk", app.HandleChunk)
mux.HandleFunc("POST /upload/finish", app.HandleFinish) mux.HandleFunc("POST /upload/finish", app.HandleFinish)
mux.HandleFunc("GET /{slug}", app.HandleGetFile) mux.HandleFunc("GET /{slug}", app.HandleGetFile)
return mux return mux
} }
func (app *App) RespondWithLink(w http.ResponseWriter, r *http.Request, key []byte, originalName string) { func (app *App) RespondWithLink(w http.ResponseWriter, r *http.Request, key []byte, originalName string) {
keySlug := base64.RawURLEncoding.EncodeToString(key) keySlug := base64.RawURLEncoding.EncodeToString(key)
ext := filepath.Ext(originalName) ext := filepath.Ext(originalName)
link := fmt.Sprintf("%s/%s%s", r.Host, keySlug, ext) link := fmt.Sprintf("%s/%s%s", r.Host, keySlug, ext)
if r.Header.Get("X-Requested-With") == "XMLHttpRequest" { if r.Header.Get("X-Requested-With") == "XMLHttpRequest" {
fmt.Fprintf(w, ` fmt.Fprintf(w, `
<div style="text-align: left;"> <div class="result-container">
<div class="dim" style="margin-bottom: 8px;">Upload Complete:</div> <div class="dim result-label">Upload Complete:</div>
<div class="copy-box"> <div class="copy-box">
<input type="text" value="%s" id="share-url" readonly onclick="this.select()"> <input type="text" value="%s" id="share-url" readonly onclick="this.select()">
<button onclick="copyToClipboard(this)">Copy</button> <button onclick="copyToClipboard(this)">Copy</button>
</div> </div>
<div class="reset-wrapper">
<button class="reset-btn" onclick="resetUI()">Upload another</button> <button class="reset-btn" onclick="resetUI()">Upload another</button>
</div>
</div>`, link) </div>`, link)
return return
} }
scheme := "https" scheme := "https"
if r.TLS == nil { if r.TLS == nil {
scheme = "http" scheme = "http"
@@ -51,7 +47,13 @@ func (app *App) RespondWithLink(w http.ResponseWriter, r *http.Request, key []by
func (app *App) SendError(w http.ResponseWriter, r *http.Request, code int) { func (app *App) SendError(w http.ResponseWriter, r *http.Request, code int) {
if r.Header.Get("X-Requested-With") == "XMLHttpRequest" { if r.Header.Get("X-Requested-With") == "XMLHttpRequest" {
w.WriteHeader(code) w.WriteHeader(code)
fmt.Fprintf(w, `<div class="error-text">Error %d</div><button class="reset-btn" onclick="resetUI()">Try again</button>`, code) fmt.Fprintf(w, `
<div class="result-container">
<div class="error-text">Error %d</div>
<div class="reset-wrapper">
<button class="reset-btn" onclick="resetUI()">Try again</button>
</div>
</div>`, code)
return return
} }
http.Error(w, http.StatusText(code), code) http.Error(w, http.StatusText(code), code)
+142 -5
View File
@@ -20,7 +20,7 @@ body {
.container { .container {
width: 100%; width: 100%;
max-width: 600px; max-width: 800px;
padding: 20px; padding: 20px;
} }
@@ -28,16 +28,33 @@ body {
margin-bottom: 30px; margin-bottom: 30px;
border-left: 3px solid var(--accent); border-left: 3px solid var(--accent);
padding-left: 16px; padding-left: 16px;
display: flex;
justify-content: space-between;
align-items: center;
}
.header-title {
margin: 0;
color: var(--header-white);
} }
.upload-area { .upload-area {
border: 2px dashed var(--border); border: 2px dashed var(--border);
border-radius: 12px; border-radius: 12px;
padding: 60px 20px; padding: 20px;
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
background: #161b22; background: #161b22;
transition: 0.2s; transition:
border-color 0.2s,
background 0.2s;
height: 220px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
box-sizing: border-box;
overflow: hidden;
} }
.upload-area:hover, .upload-area:hover,
@@ -46,6 +63,17 @@ body {
background: #1c2128; background: #1c2128;
} }
.upload-icon {
font-size: 32px;
color: var(--accent);
margin-bottom: 8px;
}
.upload-text {
font-weight: 500;
color: var(--header-white);
}
.progress-bar { .progress-bar {
height: 6px; height: 6px;
background: var(--border); background: var(--border);
@@ -53,6 +81,11 @@ body {
margin: 25px 0; margin: 25px 0;
overflow: hidden; overflow: hidden;
display: none; display: none;
width: 95%;
}
.progress-bar.visible {
display: block;
} }
.progress-fill { .progress-fill {
@@ -62,10 +95,37 @@ body {
transition: width 0.3s; transition: width 0.3s;
} }
#busy-state {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
#result-state {
width: 100%;
display: flex;
justify-content: center;
}
.result-container {
width: 100%;
max-width: 700px;
display: flex;
flex-direction: column;
padding: 0 20px;
box-sizing: border-box;
}
.result-label {
text-align: left;
margin-bottom: 8px;
}
.copy-box { .copy-box {
display: flex; display: flex;
margin-top: 20px;
gap: 8px; gap: 8px;
width: 100%;
} }
input[type="text"] { input[type="text"] {
@@ -76,7 +136,10 @@ input[type="text"] {
padding: 12px; padding: 12px;
border-radius: 6px; border-radius: 6px;
font-family: monospace; font-family: monospace;
font-size: 14px;
outline: none; outline: none;
min-width: 0;
width: 100%;
} }
button { button {
@@ -87,16 +150,27 @@ button {
border-radius: 6px; border-radius: 6px;
cursor: pointer; cursor: pointer;
font-weight: 600; font-weight: 600;
white-space: nowrap;
}
.reset-wrapper {
margin-top: 20px;
display: flex;
justify-content: center;
} }
.reset-btn { .reset-btn {
background: transparent; background: transparent;
color: var(--fg); color: var(--fg);
text-decoration: underline; text-decoration: underline;
margin-top: 20px;
border: none; border: none;
cursor: pointer; cursor: pointer;
opacity: 0.7; opacity: 0.7;
font-size: 14px;
}
.reset-btn:hover {
opacity: 1;
} }
.dim { .dim {
@@ -108,3 +182,66 @@ button {
color: #f85149; color: #f85149;
margin-bottom: 10px; margin-bottom: 10px;
} }
.github-btn {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 12px;
background: #21262d;
border: 1px solid var(--border);
border-radius: 6px;
color: var(--header-white);
text-decoration: none;
font-size: 13px;
font-weight: 500;
transition: 0.2s;
}
.github-btn:hover {
background: #30363d;
border-color: #8b949e;
}
.github-btn svg {
opacity: 0.9;
}
.cli-section {
margin-top: 40px;
padding-top: 24px;
border-top: 1px solid var(--border);
}
.cli-label {
text-transform: uppercase;
font-size: 11px;
font-weight: 700;
letter-spacing: 1px;
}
.cli-pre {
background: #161b22;
padding: 16px;
border-radius: 8px;
font-size: 13px;
overflow-x: auto;
border: 1px solid var(--border);
}
.status-text {
font-weight: 500;
}
.hidden {
display: none !important;
}
@media (max-width: 400px) {
.github-btn span {
display: none;
}
.github-btn {
padding: 6px;
}
}
+15 -6
View File
@@ -4,7 +4,7 @@ const fileInput = $("file-input");
if (dropZone) { if (dropZone) {
dropZone.onclick = () => { dropZone.onclick = () => {
if ($("idle-state").style.display !== "none") fileInput.click(); if (!$("idle-state").classList.contains("hidden")) fileInput.click();
}; };
fileInput.onchange = () => { fileInput.onchange = () => {
@@ -32,8 +32,9 @@ if (dropZone) {
} }
async function handleUpload(file) { async function handleUpload(file) {
$("idle-state").style.display = "none"; $("idle-state").classList.add("hidden");
$("busy-state").style.display = "block"; $("busy-state").classList.remove("hidden");
$("p-bar-container").classList.add("visible");
const uploadID = Math.random().toString(36).substring(2, 15); const uploadID = Math.random().toString(36).substring(2, 15);
const chunkSize = 1024 * 1024 * 8; const chunkSize = 1024 * 1024 * 8;
@@ -61,11 +62,19 @@ async function handleUpload(file) {
headers: { "X-Requested-With": "XMLHttpRequest" }, headers: { "X-Requested-With": "XMLHttpRequest" },
}); });
$("busy-state").style.display = "none"; $("busy-state").classList.add("hidden");
$("result-state").classList.remove("hidden");
$("result-state").innerHTML = await res.text(); $("result-state").innerHTML = await res.text();
} catch (e) { } catch (e) {
$("busy-state").style.display = "none"; $("busy-state").classList.add("hidden");
$("result-state").innerHTML = `<div class="error-text">Upload Failed</div><button class="reset-btn" onclick="resetUI()">Try again</button>`; $("result-state").classList.remove("hidden");
$("result-state").innerHTML = `
<div class="result-container">
<div class="error-text">Upload Failed</div>
<div class="reset-wrapper">
<button class="reset-btn" onclick="resetUI()">Try again</button>
</div>
</div>`;
} }
} }
+15 -10
View File
@@ -10,21 +10,26 @@
<body> <body>
<div class="container"> <div class="container">
<header class="header"> <header class="header">
<h2 style="margin: 0; color: var(--header-white)">safebin</h2> <div>
<h2 class="header-title">safebin</h2>
<div class="dim">Encrypted Temporary File Storage</div> <div class="dim">Encrypted Temporary File Storage</div>
</div>
<a href="https://github.com/skidoodle/safebin" class="github-btn" target="_blank" rel="noopener noreferrer">
<svg height="16" width="16" viewBox="0 0 16 16" fill="currentColor">
<path
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"
></path>
</svg>
<span>GitHub</span>
</a>
</header> </header>
{{template "content" .}} {{template "content" .}}
<section class="cli-section">
<section style="margin-top: 40px; padding-top: 24px; border-top: 1px solid var(--border)"> <div class="dim cli-label">CLI Usage</div>
<div class="dim" style="text-transform: uppercase; font-size: 11px; font-weight: 700; letter-spacing: 1px">CLI Usage</div> <pre class="cli-pre">curl -F file=@yourfile {{.Host}}</pre>
<pre style="background: #161b22; padding: 16px; border-radius: 8px; font-size: 13px; overflow-x: auto; border: 1px solid var(--border)">
curl -F file=@yourfile {{.Host}}</pre
>
</section> </section>
</div> </div>
<input type="file" id="file-input" class="hidden" />
<input type="file" id="file-input" style="display: none" />
<script src="/static/js/app.js"></script> <script src="/static/js/app.js"></script>
</body> </body>
</html> </html>
+6 -8
View File
@@ -1,18 +1,16 @@
{{define "content"}} {{define "content"}}
<main class="upload-area" id="drop-zone"> <main class="upload-area" id="drop-zone">
<div id="idle-state"> <div id="idle-state">
<div style="font-size: 32px; color: var(--accent)"></div> <div class="upload-icon"></div>
<div style="font-weight: 500; color: var(--header-white)">Click or drag to upload</div> <div class="upload-text">Click or drag to upload</div>
<div class="dim">Max size: {{.MaxMB}}MB</div> <div class="dim">Max size: {{.MaxMB}}MB</div>
</div> </div>
<div id="busy-state" class="hidden">
<div id="busy-state" style="display: none"> <div id="status-msg" class="status-text">Uploading...</div>
<div id="status-msg" style="font-weight: 500">Uploading...</div> <div class="progress-bar" id="p-bar-container">
<div class="progress-bar" id="p-bar-container" style="display: block">
<div class="progress-fill" id="p-fill"></div> <div class="progress-fill" id="p-fill"></div>
</div> </div>
</div> </div>
<div id="result-state" class="hidden"></div>
<div id="result-state"></div>
</main> </main>
{{end}} {{end}}