first version
This commit is contained in:
52
static/main.css
Normal file
52
static/main.css
Normal file
@@ -0,0 +1,52 @@
|
||||
body, html {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body, #main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.close.button {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
right: 2em;
|
||||
top: 2em;
|
||||
}
|
||||
|
||||
#dragzone[data-mode="selecting"] .close.button,
|
||||
#dragzone:not([data-mode="selected"]) #submitbtn,
|
||||
#dragzone:not([data-mode="converted"]) #downloadbtn,
|
||||
#dragzone:not([data-mode^="upload"]) .ts.progress {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#main {
|
||||
flex: 1;
|
||||
flex-grow: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#dragzone {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
#progressbar .bar {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
#upload {
|
||||
visibility: hidden;
|
||||
height: 0;
|
||||
width: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#submit {
|
||||
z-index: 5;
|
||||
}
|
||||
212
static/upload.js
Normal file
212
static/upload.js
Normal file
@@ -0,0 +1,212 @@
|
||||
const CancelToken = axios.CancelToken;
|
||||
let cancel;
|
||||
|
||||
const dqs = (selector, ctx = document) => {
|
||||
return ctx.querySelector(selector);
|
||||
}
|
||||
|
||||
HTMLElement.prototype.on = function(event, callback) {
|
||||
this.addEventListener(event, callback);
|
||||
return this;
|
||||
}
|
||||
|
||||
function updateFile(files) {
|
||||
let filename = files[0].name;
|
||||
let size = files[0].size;
|
||||
|
||||
// check file extension
|
||||
if (filename.split(".").pop() != "epub") {
|
||||
ts(".ts.snackbar").snackbar({
|
||||
content: "只接受 EPUB 格式的檔案!"
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// check file size
|
||||
if (size >= sizeLimit) {
|
||||
ts(".ts.snackbar").snackbar({
|
||||
content: "檔案過大!"
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!dqs("#upload").files.length) {
|
||||
dqs("#upload").files = files;
|
||||
}
|
||||
|
||||
dqs(".header", dqs("#dragzone")).textContent = filename;
|
||||
dqs(".description", dqs("#dragzone")).textContent = `檔案大小: ${humanFileSize(size, false)}`;
|
||||
dqs("#dragzone").dataset.mode = "selected";
|
||||
}
|
||||
|
||||
function reset(ev) {
|
||||
if (ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
}
|
||||
dqs(".header", dqs("#dragzone")).textContent = "上傳";
|
||||
dqs(".description", dqs("#dragzone")).innerHTML = "將檔案拖拉至此處進行上傳,或是點擊此處選取檔案。<br>Max upload size : " + humanFileSize(sizeLimit, false);
|
||||
dqs("#dragzone").dataset.mode = "selecting";
|
||||
dqs("#upload").value = "";
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/14919494
|
||||
function humanFileSize(bytes, si) {
|
||||
var thresh = si ? 1000 : 1024;
|
||||
if(Math.abs(bytes) < thresh) {
|
||||
return bytes + ' B';
|
||||
}
|
||||
var units = si
|
||||
? ['kB','MB','GB','TB','PB','EB','ZB','YB']
|
||||
: ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB'];
|
||||
var u = -1;
|
||||
do {
|
||||
bytes /= thresh;
|
||||
++u;
|
||||
} while (Math.abs(bytes) >= thresh && u < units.length - 1);
|
||||
return bytes.toFixed(1)+' '+units[u];
|
||||
}
|
||||
|
||||
dqs("#upload").on("change", ev => {
|
||||
let el = ev.target;
|
||||
if (el.files.length) {
|
||||
if (el.files.length > 1) {
|
||||
ts('.snackbar').snackbar({
|
||||
content: "一次僅可上傳一個檔案。"
|
||||
});
|
||||
} else {
|
||||
updateFile(el.files);
|
||||
}
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
dqs(".ts.close.button").on("click", ev => {
|
||||
if (dqs("#dragzone").dataset.mode == "uploading") {
|
||||
if (cancel) {
|
||||
cancel();
|
||||
}
|
||||
}
|
||||
reset();
|
||||
});
|
||||
|
||||
dqs("#submitbtn").on("click", ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
|
||||
dqs("#dragzone").dataset.mode = "uploading";
|
||||
|
||||
// clean up styles
|
||||
["preparing", "positive", "negative"].forEach(c => {
|
||||
dqs("#progressbar").classList.toggle(c, false);
|
||||
});
|
||||
|
||||
dqs("#progressbar .bar").style.width = "0";
|
||||
if (dqs("#downloadbtn").href) {
|
||||
window.URL.revokeObjectURL(dqs("#downloadbtn").href);
|
||||
dqs("#downloadbtn").href = "";
|
||||
dqs("#downloadbtn").removeAttribute("download");
|
||||
}
|
||||
|
||||
axios.post("./api/convert", new FormData(document.form), {
|
||||
responseType: "blob",
|
||||
cancelToken: new CancelToken(function (executor) {
|
||||
cancel = executor;
|
||||
}),
|
||||
onUploadProgress: (ev) => {
|
||||
percentage = (ev.loaded / ev.total) * 100
|
||||
dqs("#progressbar .bar").style.width = percentage + "%";
|
||||
if (percentage == 100) {
|
||||
dqs("#progressbar").classList.add("preparing");
|
||||
}
|
||||
}
|
||||
}).then(function (res) {
|
||||
dqs("#dragzone").dataset.mode = "converted";
|
||||
dqs("#progressbar").classList.remove("preparing");
|
||||
|
||||
let blob = new Blob([res.data], { type: "application/epub+zip" });
|
||||
let disposition = res.headers['content-disposition'];
|
||||
let filename = disposition.slice(disposition.lastIndexOf("=") + 1, disposition.length);
|
||||
if (filename.startsWith("UTF-8''")) {
|
||||
filename = decodeURIComponent(filename.slice(7, filename.length));
|
||||
}
|
||||
dqs("#downloadbtn").href = window.URL.createObjectURL(blob);
|
||||
dqs("#downloadbtn").setAttribute("download", filename);
|
||||
}).catch(function (e) {
|
||||
dqs("#dragzone").dataset.mode = "uploadend";
|
||||
dqs("#progressbar").classList.remove("preparing");
|
||||
dqs("#progressbar").classList.add("negative");
|
||||
if (e.response) {
|
||||
if (e.response.data instanceof Blob && e.response.data.type == "application/json") {
|
||||
let reader = new FileReader();
|
||||
reader.onload = function () {
|
||||
let data = JSON.parse(this.result);
|
||||
ts(".snackbar").snackbar({
|
||||
content: `錯誤: ${data.error}`
|
||||
});
|
||||
}
|
||||
reader.readAsText(e.response.data);
|
||||
}
|
||||
} else if (axios.isCancel(e)) {
|
||||
console.log("Upload progress canceled");
|
||||
dqs("#progressbar").classList.remove("negative");
|
||||
ts(".snackbar").snackbar({
|
||||
content: "上傳已取消"
|
||||
});
|
||||
} else {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
dqs("#dragzone").on("click", ev => {
|
||||
ev.preventDefault();
|
||||
|
||||
if (dqs("#dragzone").dataset.mode != "uploading") {
|
||||
let allowlist = ["button", "a"];
|
||||
if (allowlist.indexOf(ev.target.tagName.toLowerCase()) == -1) {
|
||||
dqs("#upload").click();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
dqs("#downloadbtn").on("click", ev => {
|
||||
ev.preventDefault();
|
||||
let el = ev.target;
|
||||
|
||||
let link = document.createElement("a");
|
||||
link.setAttribute("download", el.getAttribute("download"));
|
||||
link.style.display = "none";
|
||||
link.href = el.href;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
reset();
|
||||
});
|
||||
|
||||
dqs("#dragzone").on("drop", ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
|
||||
if (dqs("#dragzone").dataset.mode != "uploading") {
|
||||
let files = ev.dataTransfer.files;
|
||||
if (files) {
|
||||
if (files.length > 1) {
|
||||
ts('.snackbar').snackbar({
|
||||
content: "一次僅可上傳一個檔案。"
|
||||
});
|
||||
} else if (files.length == 1) {
|
||||
updateFile(files);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
["dragenter", "dragover"].forEach(event => {
|
||||
dqs("#dragzone").on(event, ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user