fup: implement frontend bits
This commit is contained in:
parent
45b3a1fe44
commit
d05aa3ace1
5 changed files with 294 additions and 5 deletions
|
@ -3,3 +3,135 @@
|
|||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
html {
|
||||
--colour-primary: #32681d;
|
||||
--colour-primary-light: #619648;
|
||||
--colour-primary-dark: #003d00;
|
||||
|
||||
--colour-text-primary: #ffffff;
|
||||
--colour-text-primary-dark: #ffffff;
|
||||
--colour-text-primary-light: #000000;
|
||||
|
||||
--colour-secondary: #cddc39;
|
||||
--colour-secondary-light: #ffff6e;
|
||||
--colour-secondary-dark: #99aa00;
|
||||
|
||||
--colour-text-secondary: #424242;
|
||||
--colour-text-secondary-dark: #424242;
|
||||
--colour-text-secondary-light: #424242;
|
||||
|
||||
--colour-canvas: #e1e2e1;
|
||||
--colour-canvas-light: #f5f5f6;
|
||||
--colour-text: #424242;
|
||||
}
|
||||
|
||||
html {
|
||||
color: var(--colour-text);
|
||||
background: var(--colour-canvas);
|
||||
font-family: "Roboto Sans", sans-serif;
|
||||
}
|
||||
|
||||
body:not(.has-js) .with-js, body.has-js .no-js {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
header {
|
||||
border-bottom: 1px solid var(--colour-primary-dark);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
header > h1 {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
header > h1 > a, header > h1 > a:visited {
|
||||
color: var(--colour-text);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
nav a:after {
|
||||
display: inline;
|
||||
content: "|";
|
||||
padding-left: 0.3em;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
nav a:last-of-type:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--colour-primary);
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: var(--colour-primary-dark);
|
||||
}
|
||||
|
||||
header, .container {
|
||||
padding: 10px 30px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: "Roboto Mono", monospace;
|
||||
}
|
||||
|
||||
.dropbox {
|
||||
background: var(--colour-canvas-light);
|
||||
height: 10rem;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.upload-list {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.upload-list-element {
|
||||
position: relative;
|
||||
display: block;
|
||||
border: 1px solid var(--colour-primary-dark);
|
||||
margin-top: 0.4rem;
|
||||
padding: 0 10px;
|
||||
|
||||
background: var(--colour-primary-light);
|
||||
}
|
||||
|
||||
.upload-list-element, .upload-list-element a, .upload-list-element a:visited {
|
||||
color: var(--colour-text-primary-light);
|
||||
}
|
||||
|
||||
.upload-list-bar {
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
left: 0;
|
||||
background: var(--colour-primary-dark);
|
||||
height: calc(100% + 2px);
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.upload-list-bar-container {
|
||||
padding: 1px 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.upload-list-bar, .upload-list-bar a, .upload-list-bar a:visited {
|
||||
color: var(--colour-text-primary-dark);
|
||||
}
|
||||
|
||||
.uploaded {
|
||||
background: var(--colour-primary-dark);
|
||||
}
|
||||
.uploaded a, .uploaded a:visited {
|
||||
color: var(--colour-text-primary-dark);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,121 @@
|
|||
// SPDX-FileCopyrightText: 2021 Luke Granger-Brown <depot@lukegb.com>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
document.body.classList.add('has-js');
|
||||
|
||||
if (document.body.classList.contains('upload-page')) {
|
||||
const uploadListEl = document.body.querySelector('.upload-list');
|
||||
const inputFileEl = document.body.querySelector('#file');
|
||||
const expiryEl = document.body.querySelector('#expiry');
|
||||
|
||||
const uploadFile = (file, expiry) => {
|
||||
const progressEl = document.createElement('li');
|
||||
progressEl.classList.add('upload-list-element');
|
||||
uploadListEl.appendChild(progressEl);
|
||||
|
||||
const bgEl = document.createElement('div');
|
||||
bgEl.classList.add('upload-list-background');
|
||||
progressEl.appendChild(bgEl);
|
||||
|
||||
const bgFilenameEl = document.createElement('span');
|
||||
bgFilenameEl.classList.add('upload-list-filename');
|
||||
bgFilenameEl.textContent = file.name;
|
||||
bgEl.appendChild(bgFilenameEl);
|
||||
|
||||
const bgSpacerNode = document.createTextNode(' ');
|
||||
bgEl.appendChild(bgSpacerNode);
|
||||
|
||||
const bgPercentEl = document.createElement('span');
|
||||
bgPercentEl.classList.add('upload-list-progress');
|
||||
bgPercentEl.textContent = file.size == 0 ? '(unknown)' : '(0%)';
|
||||
bgEl.appendChild(bgPercentEl);
|
||||
|
||||
const barEl = document.createElement('div');
|
||||
barEl.classList.add('upload-list-bar');
|
||||
progressEl.appendChild(barEl);
|
||||
|
||||
const barContainerEl = document.createElement('div');
|
||||
barContainerEl.classList.add('upload-list-bar-container');
|
||||
barEl.appendChild(barContainerEl);
|
||||
|
||||
const barFilenameEl = document.createElement('span');
|
||||
barFilenameEl.classList.add('upload-list-filename');
|
||||
barFilenameEl.textContent = file.name;
|
||||
barContainerEl.appendChild(barFilenameEl);
|
||||
|
||||
const barSpacerNode = document.createTextNode(' ');
|
||||
barContainerEl.appendChild(barSpacerNode);
|
||||
|
||||
const barPercentEl = document.createElement('span');
|
||||
barPercentEl.classList.add('upload-list-progress');
|
||||
barPercentEl.textContent = file.size == 0 ? '(unknown)' : '(0%)';
|
||||
barContainerEl.appendChild(barPercentEl);
|
||||
|
||||
const setPercentText = (txt) => {
|
||||
barPercentEl.textContent = txt;
|
||||
bgPercentEl.textContent = txt;
|
||||
};
|
||||
const setPercentPercent = (loaded, total) => {
|
||||
const pct = Math.floor((loaded * 1000) / total) / 10;
|
||||
setPercentText(`(${pct}%)`);
|
||||
barEl.style.width = `${pct}%`;
|
||||
};
|
||||
|
||||
const hdrs = new Headers();
|
||||
hdrs.set('Content-Type', file.type);
|
||||
hdrs.set('Accept', 'application/json');
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.upload.addEventListener('progress', (ev) => {
|
||||
if (!ev.lengthComputable) {
|
||||
setPercentText(`(unknown: ${ev.loaded} bytes)`);
|
||||
} else {
|
||||
setPercentPercent(ev.loaded, ev.total);
|
||||
}
|
||||
});
|
||||
xhr.upload.addEventListener('load', (ev) => {
|
||||
setPercentPercent(1, 1);
|
||||
});
|
||||
xhr.addEventListener('error', (ev) => {
|
||||
setPercentText(`(error: ${ev.message})`);
|
||||
});
|
||||
xhr.addEventListener('abort', (ev) => {
|
||||
setPercentText(`(aborted)`);
|
||||
});
|
||||
xhr.addEventListener('load', (ev) => {
|
||||
if (ev.status < 200 || ev.status >= 300) {
|
||||
setPercentText(`(error: HTTP status ${ev.status} - ${ev.message})`);
|
||||
return;
|
||||
}
|
||||
|
||||
const respJSON = xhr.response;
|
||||
|
||||
while (progressEl.firstChild) {
|
||||
progressEl.removeChild(progressEl.firstChild);
|
||||
}
|
||||
progressEl.classList.add('uploaded');
|
||||
|
||||
const linkEl = document.createElement('a');
|
||||
linkEl.setAttribute('href', respJSON.display_url);
|
||||
linkEl.textContent = file.name;
|
||||
progressEl.appendChild(linkEl);
|
||||
});
|
||||
xhr.responseType = 'json';
|
||||
xhr.open('PUT', `/upload/${encodeURIComponent(file.name)}`);
|
||||
xhr.setRequestHeader('Content-Type', file.type);
|
||||
xhr.setRequestHeader('Accept', 'application/json');
|
||||
if (expiry !== "") {
|
||||
xhr.setRequestHeader('Fup-Expiry', expiry);
|
||||
}
|
||||
xhr.send(file);
|
||||
};
|
||||
|
||||
inputFileEl.addEventListener('change', (ev) => {
|
||||
for (let i = 0; i < inputFileEl.files.length; i++) {
|
||||
const file = inputFileEl.files[i];
|
||||
|
||||
uploadFile(file, expiryEl.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -5,6 +5,6 @@ SPDX-License-Identifier: Apache-2.0
|
|||
*/}}
|
||||
|
||||
{{define "main"}}
|
||||
<h1>404 Not Found</h1>
|
||||
<h2>404 Not Found</h2>
|
||||
<p>Sorry. :(</p>
|
||||
{{end}}
|
||||
|
|
|
@ -10,9 +10,20 @@ SPDX-License-Identifier: Apache-2.0
|
|||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" type="text/css" href="{{static "css/reset.min.css"}}">
|
||||
<link rel="stylesheet" type="text/css" href="{{static "css/base.css"}}">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto&family=Roboto+Mono&display=swap" rel="stylesheet">
|
||||
<script type="module" src="{{ static "js/base.js"}}"></script>
|
||||
</head>
|
||||
<body>
|
||||
<body class="{{block "extra_body_classes" .}}{{end}}">
|
||||
<header>
|
||||
<h1><a href="{{app "/"}}">lukegb</a></h1>
|
||||
<nav>
|
||||
<a href="/">Upload</a>
|
||||
<a href="/paste">Paste</a>
|
||||
</nav>
|
||||
</header>
|
||||
<div class="container">
|
||||
{{block "main" .}}{{end}}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -5,5 +5,33 @@ SPDX-License-Identifier: Apache-2.0
|
|||
*/}}
|
||||
|
||||
{{define "main"}}
|
||||
<h1>Hello</h1>
|
||||
<label for="file">
|
||||
<div class="dropbox with-js">
|
||||
Click or drop to upload files
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<form action="{{app "upload"}}" method="POST" enctype="multipart/form-data">
|
||||
<div class="no-js">
|
||||
<label for="file">Select file:</label> <input type="file" name="file" id="file" multiple> <input type="submit" value="Upload!">
|
||||
</div>
|
||||
|
||||
<label for="expiry">Expiry:</label>
|
||||
<select name="expiry" id="expiry">
|
||||
<option value="" selected>never</option>
|
||||
<option value="1m">1 minute</option>
|
||||
<option value="5m">5 minutes</option>
|
||||
<option value="1h">1 hour</option>
|
||||
<option value="24h">1 day</option>
|
||||
<option value="168h">1 week</option>
|
||||
<option value="672h">4 weeks</option>
|
||||
<option value="8760h">1 year</option>
|
||||
</select>
|
||||
</form>
|
||||
|
||||
<ol class="upload-list"></ol>
|
||||
{{end}}
|
||||
|
||||
{{define "extra_body_classes" -}}
|
||||
upload-page
|
||||
{{- end}}
|
||||
|
|
Loading…
Reference in a new issue