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 globalStore store
var handleAssets = http.FileServer(http.FS(assetsWeb))
func setupHandlers() { func setupHandlers() {
var err error var err error
@ -23,10 +22,29 @@ func setupHandlers() {
} }
if debug { 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 */ /* credentials */
func getCredential(id string) (credential, error) { func getCredential(id string) (credential, error) {
@ -59,17 +77,6 @@ func setCredential(id string, cred credential, expire time.Duration) error {
return nil 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 */ /* upload template */
func handleUpload(w http.ResponseWriter, req *http.Request) { func handleUpload(w http.ResponseWriter, req *http.Request) {

View File

@ -24,7 +24,7 @@ func main() {
router.Use(middlewareLogger) router.Use(middlewareLogger)
router.Methods(http.MethodGet).Path("/readyz").HandlerFunc(readyz) 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) router.Methods(http.MethodGet).Path("/create").HandlerFunc(handleCreate)
uploadRouter := router.PathPrefix("/{id}").Subrouter() uploadRouter := router.PathPrefix("/{id}").Subrouter()

View File

@ -95,7 +95,8 @@
id="options-expiry-number" id="options-expiry-number"
name="ExpiryNumber" name="ExpiryNumber"
value="7" value="7"
min="0"> min="0"
max="1000">
<select <select
class="rounded-r-md border-gray-400" class="rounded-r-md border-gray-400"
id="options-expiry-unit" id="options-expiry-unit"
@ -107,8 +108,13 @@
</div> </div>
<p class="mt-1 text-sm text-gray-500"> <p class="mt-1 text-sm text-gray-500">
Time until the link expires, and credentials are purged from the server. Time until the link expires, and credentials are purged from the server.
<span class="text-red-700" id="expiry-notice"></span>
</p> </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> </div>
</section> </section>

View File

@ -1,4 +1,6 @@
</main> </main>
<script src="assets/bundle.js"></script>
</body> </body>
</html> </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() { 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); this.items.push(...loaded);
} }
localStorageSave() { localStorageSave() {
window.localStorage.setItem("log" + this.key, JSON.stringify(this.items)); window.localStorage.setItem('log' + this.key, JSON.stringify(this.items));
} }
static renderItem(item) { static renderItem(item) {
@ -25,9 +25,9 @@ class Log {
const url = document.createElement('input'); const url = document.createElement('input');
url.value = item.location; url.value = item.location;
url.setAttribute("readonly", ""); url.setAttribute('readonly', '');
url.classList.add('log-url'); url.classList.add('log-url');
url.addEventListener("click", (e) => { url.addEventListener('click', (e) => {
e.target.setSelectionRange(0, e.target.value.length); e.target.setSelectionRange(0, e.target.value.length);
}); });
base.appendChild(url); base.appendChild(url);
@ -42,7 +42,7 @@ class Log {
render() { render() {
const elements = this.items.map(this.constructor.renderItem); const elements = this.items.map(this.constructor.renderItem);
this.target.innerHTML = ""; this.target.innerHTML = '';
elements.forEach(element => { elements.forEach(element => {
this.target.appendChild(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 */ /* CSS */
import 'tailwindcss/tailwind.css'; import 'tailwindcss/tailwind.css';
@ -11,38 +6,8 @@ import '@uppy/core/dist/style.css';
import '@uppy/drag-drop/dist/style.css'; import '@uppy/drag-drop/dist/style.css';
import '@uppy/status-bar/dist/style.css'; import '@uppy/status-bar/dist/style.css';
/* Components */ /* Magic */
import Log from './log'; import './save';
import './derive';
const log = new Log('#log-area', window.location.pathname); import './upload';
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,
});
});

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 id="log-area"></div>
</div> </div>
</div> </div>
<script src="assets/bundle.js"></script>
{{template "foot.tmpl"}} {{template "foot.tmpl"}}