Add input deriver
parent
4a3c93dbbb
commit
5961c202eb
33
handlers.go
33
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) {
|
||||
|
|
2
main.go
2
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()
|
||||
|
|
|
@ -95,7 +95,8 @@
|
|||
id="options-expiry-number"
|
||||
name="ExpiryNumber"
|
||||
value="7"
|
||||
min="0">
|
||||
min="0"
|
||||
max="1000">
|
||||
<select
|
||||
class="rounded-r-md border-gray-400"
|
||||
id="options-expiry-unit"
|
||||
|
@ -107,8 +108,13 @@
|
|||
</div>
|
||||
<p class="mt-1 text-sm text-gray-500">
|
||||
Time until the link expires, and credentials are purged from the server.
|
||||
<span class="text-red-700" id="expiry-notice"></span>
|
||||
</p>
|
||||
<input type="hidden" name="Expiry" value="" data-derive="duration,ExpiryNumber,ExpiryUnits">
|
||||
<input
|
||||
type="hidden"
|
||||
name="Expiry"
|
||||
data-derive="duration,ExpiryNumber,ExpiryUnits"
|
||||
data-derive-notice="#expiry-notice">
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
|
||||
</main>
|
||||
|
||||
<script src="assets/bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -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();
|
||||
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
|
||||
});
|
|
@ -10,6 +10,4 @@
|
|||
<div id="log-area"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="assets/bundle.js"></script>
|
||||
{{template "foot.tmpl"}}
|
||||
|
|
Loading…
Reference in New Issue