1
0
Fork 0

Add input deriver

upload-progress
Ambrose Chua 2021-05-23 17:00:48 +08:00
parent 4a3c93dbbb
commit 5961c202eb
12 changed files with 189 additions and 62 deletions

View File

@ -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) {

View File

@ -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()

View File

@ -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>

View File

@ -1,4 +1,6 @@
</main>
<script src="assets/bundle.js"></script>
</body>
</html>

92
web/src/derive.js Normal file
View File

@ -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();
});

View File

@ -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);
});

View File

View File

@ -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';

6
web/src/time.js Normal file
View File

@ -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;

51
web/src/upload.js Normal file
View File

@ -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,
});
});
});

View File

@ -10,6 +10,4 @@
<div id="log-area"></div>
</div>
</div>
<script src="assets/bundle.js"></script>
{{template "foot.tmpl"}}