diff --git a/handlers.go b/handlers.go index dcfa833..d32c326 100644 --- a/handlers.go +++ b/handlers.go @@ -13,7 +13,6 @@ import ( ) var globalStore store -var handleAssets = http.FileServer(http.FS(assetsWeb)) func setupHandlers() { var err error @@ -23,10 +22,29 @@ func setupHandlers() { } if debug { - handleAssets = http.FileServer(http.FS(os.DirFS("web/assets/"))) + assetsServer = http.FileServer(http.FS(os.DirFS("web"))) } } +/* assets */ + +var assetsServer = http.FileServer(http.FS(assetsWeb)) + +func handleAssets(w http.ResponseWriter, req *http.Request) { + assetsServer.ServeHTTP(w, req) +} + +/* templates */ + +var tmpl = template.Must(template.ParseFS(assets, "web/*.tmpl")) + +func executeTemplate(w io.Writer, name string, data interface{}) error { + if debug { + tmpl = template.Must(template.ParseGlob("web/*.tmpl")) + } + return tmpl.ExecuteTemplate(w, name, nil) +} + /* credentials */ func getCredential(id string) (credential, error) { @@ -59,17 +77,6 @@ func setCredential(id string, cred credential, expire time.Duration) error { return nil } -/* templates */ - -var tmpl = template.Must(template.ParseFS(assets, "web/*.tmpl")) - -func executeTemplate(w io.Writer, name string, data interface{}) error { - if debug { - tmpl = template.Must(template.ParseGlob("web/*.tmpl")) - } - return tmpl.ExecuteTemplate(w, name, nil) -} - /* upload template */ func handleUpload(w http.ResponseWriter, req *http.Request) { diff --git a/main.go b/main.go index 3965594..b941251 100644 --- a/main.go +++ b/main.go @@ -24,7 +24,7 @@ func main() { router.Use(middlewareLogger) router.Methods(http.MethodGet).Path("/readyz").HandlerFunc(readyz) - router.Methods(http.MethodGet).PathPrefix("/assets").Handler(handleAssets) + router.Methods(http.MethodGet).PathPrefix("/assets").HandlerFunc(handleAssets) router.Methods(http.MethodGet).Path("/create").HandlerFunc(handleCreate) uploadRouter := router.PathPrefix("/{id}").Subrouter() diff --git a/web/create.tmpl b/web/create.tmpl index 6710341..adfaa9c 100644 --- a/web/create.tmpl +++ b/web/create.tmpl @@ -95,7 +95,8 @@ id="options-expiry-number" name="ExpiryNumber" value="7" - min="0"> + min="0" + max="1000"> + diff --git a/web/foot.tmpl b/web/foot.tmpl index a0357db..ea9108e 100644 --- a/web/foot.tmpl +++ b/web/foot.tmpl @@ -1,4 +1,6 @@ + + diff --git a/web/src/derive.js b/web/src/derive.js new file mode 100644 index 0000000..99afabe --- /dev/null +++ b/web/src/derive.js @@ -0,0 +1,92 @@ +import * as time from './time'; + +class InvalidInputError extends Error { + constructor(problems) { + super(`Invalid input: ${problems.join(', ')}`); + this.problems = problems; + } +} + +class Deriver { + constructor(output, inputs, notice=null) { + this.output = output; + this.inputs = inputs; + this.notice = notice; + } + + get values() { + return this.inputs.map(input => input.value); + } + + bind() { + this.inputs.forEach(input => { + input.addEventListener('input', () => { + this.update(); + }); + }); + } + + update() { + try { + const output = this.derive(); + this.output.value = output; + this.showError(); + } catch (e) { + console.error(e); + this.showError(e); + } + } + + showError(error='') { + if (!this.notice) { + return; + } + let message = error.toString(); + if (Array.isArray(error.problems)) { + message = 'Invalid input: ' + error.problems.join(', '); + } + this.notice.innerText = message; + } +} + +class DurationDeriver extends Deriver { + derive() { + const [ number, units ] = this.values; + const n = parseInt(number, 10); + if (!isFinite(n)) { + throw new InvalidInputError([`provided duration is not a number`]); + } + switch (units) { + case 's': + return n * time.SECOND; + case 'm': + return n * time.MINUTE; + case 'h': + return n * time.HOUR; + case 'd': + return n * 24 * time.HOUR; + } + throw new InvalidInputError([`unit ${units} is not valid`]); + } +} + +const derivers = { + duration: DurationDeriver, +}; + +const deriveInputs = document.querySelectorAll('[data-derive]'); +deriveInputs.forEach(deriveInput => { + + const [ type, ...inputNames ] = deriveInput.dataset.derive.split(','); + if (!type in derivers) { + return; + } + + const inputs = inputNames.map(inputName => document.querySelector(`[name="${inputName}"]`)); + const notice = document.querySelector(deriveInput.dataset.deriveNotice); + + const deriver = new derivers[type](deriveInput, inputs, notice); + deriver.bind(); + deriver.update(); + +}); diff --git a/web/src/log.js b/web/src/log.js index 82003ff..8230e7a 100644 --- a/web/src/log.js +++ b/web/src/log.js @@ -11,12 +11,12 @@ class Log { } localStorageLoad() { - const loaded = JSON.parse(window.localStorage.getItem("log" + this.key) || "[]"); + const loaded = JSON.parse(window.localStorage.getItem('log' + this.key) || '[]'); this.items.push(...loaded); } localStorageSave() { - window.localStorage.setItem("log" + this.key, JSON.stringify(this.items)); + window.localStorage.setItem('log' + this.key, JSON.stringify(this.items)); } static renderItem(item) { @@ -25,9 +25,9 @@ class Log { const url = document.createElement('input'); url.value = item.location; - url.setAttribute("readonly", ""); + url.setAttribute('readonly', ''); url.classList.add('log-url'); - url.addEventListener("click", (e) => { + url.addEventListener('click', (e) => { e.target.setSelectionRange(0, e.target.value.length); }); base.appendChild(url); @@ -42,7 +42,7 @@ class Log { render() { const elements = this.items.map(this.constructor.renderItem); - this.target.innerHTML = ""; + this.target.innerHTML = ''; elements.forEach(element => { this.target.appendChild(element); }); diff --git a/web/src/main.css b/web/src/main.css deleted file mode 100644 index e69de29..0000000 diff --git a/web/src/main.js b/web/src/main.js index b660d44..86df742 100644 --- a/web/src/main.js +++ b/web/src/main.js @@ -1,8 +1,3 @@ -import Uppy from '@uppy/core'; -import DragDrop from '@uppy/drag-drop'; -import StatusBar from '@uppy/status-bar'; -import AwsS3Multipart from '@uppy/aws-s3-multipart'; - /* CSS */ import 'tailwindcss/tailwind.css'; @@ -11,38 +6,8 @@ import '@uppy/core/dist/style.css'; import '@uppy/drag-drop/dist/style.css'; import '@uppy/status-bar/dist/style.css'; -/* Components */ +/* Magic */ -import Log from './log'; - -const log = new Log('#log-area', window.location.pathname); -document.querySelector('#log-clear').addEventListener('click', () => { - log.clear(); -}); - -/* Uppy */ - -const uppy = new Uppy({ - autoProceed: true, -}); -uppy.use(DragDrop, { - target: '#drop-area', - height: '16rem', -}); -uppy.use(StatusBar, { - target: '#status-area', -}); -uppy.use(AwsS3Multipart, { - limit: 3, - companionUrl: window.location.pathname, -}); - -/* Uppy handlers */ - -uppy.on('upload-success', (f, res) => { - log.add({ - name: f.name, - size: f.size, - location: res.body.Location, - }); -}); +import './save'; +import './derive'; +import './upload'; diff --git a/web/src/log.css b/web/src/save.js similarity index 100% rename from web/src/log.css rename to web/src/save.js diff --git a/web/src/time.js b/web/src/time.js new file mode 100644 index 0000000..2d1b203 --- /dev/null +++ b/web/src/time.js @@ -0,0 +1,6 @@ +export const NANOSECOND = 1; +export const MICROSECOND = 1000 * NANOSECOND; +export const MILLISECOND = 1000 * MICROSECOND; +export const SECOND = 1000 * MILLISECOND; +export const MINUTE = 60 * SECOND; +export const HOUR = 60 * MINUTE; diff --git a/web/src/upload.js b/web/src/upload.js new file mode 100644 index 0000000..a654058 --- /dev/null +++ b/web/src/upload.js @@ -0,0 +1,51 @@ +import Uppy from '@uppy/core'; +import DragDrop from '@uppy/drag-drop'; +import StatusBar from '@uppy/status-bar'; +import AwsS3Multipart from '@uppy/aws-s3-multipart'; + +import Log from './log'; + +const uploadAreas = document.querySelectorAll('.upload'); +uploadAreas.forEach(uploadArea => { + + /* Elements */ + + const logArea = uploadArea.querySelector('.log-area'); + const logClearBtn = uploadArea.querySelector('.log-clear') + const dropArea = uploadArea.querySelector('.drop-area'); + const statusArea = uploadArea.querySelector('.status-area'); + + /* Components */ + + const log = new Log(logArea, window.location.pathname); + + logClearBtn.addEventListener('click', () => { + log.clear(); + }); + + /* Uppy */ + + const uppy = new Uppy({ + autoProceed: true, + }); + uppy.use(DragDrop, { + target: dropArea, + height: '16rem', + }); + uppy.use(StatusBar, { + target: statusArea, + }); + uppy.use(AwsS3Multipart, { + limit: 3, + companionUrl: window.location.pathname, + }); + + uppy.on('upload-success', (f, res) => { + log.add({ + name: f.name, + size: f.size, + location: res.body.Location, + }); + }); + +}); diff --git a/web/upload.tmpl b/web/upload.tmpl index 3ff6d41..db5710a 100644 --- a/web/upload.tmpl +++ b/web/upload.tmpl @@ -10,6 +10,4 @@
- - {{template "foot.tmpl"}}