diff --git a/web/fup/fupstatic/css/base.css b/web/fup/fupstatic/css/base.css index 81d4aed960..fcf4a022f0 100644 --- a/web/fup/fupstatic/css/base.css +++ b/web/fup/fupstatic/css/base.css @@ -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); +} diff --git a/web/fup/fupstatic/js/base.js b/web/fup/fupstatic/js/base.js index b154b0c1c1..a665b41b2f 100644 --- a/web/fup/fupstatic/js/base.js +++ b/web/fup/fupstatic/js/base.js @@ -1,3 +1,121 @@ // SPDX-FileCopyrightText: 2021 Luke Granger-Brown // -// SPDX-License-Identifier: Apache-2.0 \ No newline at end of file +// 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); + } + }); +} diff --git a/web/fup/fupstatic/tmpl/404.html b/web/fup/fupstatic/tmpl/404.html index 869d7d7efd..ada04af571 100644 --- a/web/fup/fupstatic/tmpl/404.html +++ b/web/fup/fupstatic/tmpl/404.html @@ -5,6 +5,6 @@ SPDX-License-Identifier: Apache-2.0 */}} {{define "main"}} -

404 Not Found

+

404 Not Found

Sorry. :(

{{end}} diff --git a/web/fup/fupstatic/tmpl/base.html b/web/fup/fupstatic/tmpl/base.html index 43a685fdc9..8930294078 100644 --- a/web/fup/fupstatic/tmpl/base.html +++ b/web/fup/fupstatic/tmpl/base.html @@ -10,9 +10,20 @@ SPDX-License-Identifier: Apache-2.0 + + - - {{block "main" .}}{{end}} + +
+

lukegb

+ +
+
+ {{block "main" .}}{{end}} +
diff --git a/web/fup/fupstatic/tmpl/index.html b/web/fup/fupstatic/tmpl/index.html index bc9c323568..7160f3b4c4 100644 --- a/web/fup/fupstatic/tmpl/index.html +++ b/web/fup/fupstatic/tmpl/index.html @@ -5,5 +5,33 @@ SPDX-License-Identifier: Apache-2.0 */}} {{define "main"}} -

Hello

+ + +
+
+ +
+ + + +
+ +
    {{end}} + +{{define "extra_body_classes" -}} +upload-page +{{- end}}