diff --git a/client/app.js b/client/app.js index d44fe90..540136d 100644 --- a/client/app.js +++ b/client/app.js @@ -71,6 +71,8 @@ app.addSetter('load.board', async (data, force) => { }); app.addSetter('load.user', async (data) => { + + console.log("load.user"); store.do("loader.show"); let res = await fetch("/api/whoami"); @@ -96,6 +98,102 @@ app.addSetter("hash.update", (data) => { } }); +app.addSetter("app.uploadDroppedFiles", async (data, evt) => { + + let boardId = store.data.board.id; + + if ( boardId ){ + let hasFiles = event.dataTransfer.types.find(i => i == "Files") == "Files"; + if ( hasFiles ){ + + if ( evt.dataTransfer.items ){ + + let files = []; + + for ( let i = 0; i < evt.dataTransfer.items.length; ++i ){ + if ( evt.dataTransfer.items[i].kind === "file" ){ + let file = evt.dataTransfer.items[i].getAsFile(); + + if ( file.type != "image/jpeg" && file.type != "image/png" ){ + + window.alert("Unsupported file type. JPEG and PNG images are supported."); + console.log("Unsupported file type: " + file.type); + + return; + } + + // check size + if ( file.size >= 26214400 ){ + window.alert("File size exceeds the 25MB limit."); + console.log("File size exceeds the 25MB limit. size=" + file.size); + document.getElementById("fileInput").value = ""; + return; + } + + files.push(file); + + } + } + + console.log("Number of files=" + files.length); + + for ( let i = 0; i < files.length; ++i ){ + + data.dropUploadMessage = `Uploading ${i+1} of ${files.length}`; + + try { + let newPin = await multipartUpload(files[i], boardId); + if ( data.board && data.board.id == boardId ){ + data.board.pins.push(newPin); + } + } catch (e){ + window.alert("Error uploading images."); + break; + } + } + + data.dropUploadMessage = null; + + } + + } + } + +}); + +function PostException(statusCode, errorMessage){ + this.statusCode = statusCode; + this.errorMessage = errorMessage; +} + +async function multipartUpload(file, boardId, newBoardName, siteUrl, description){ + console.log("attempting multipart upload"); + let formData = new FormData(); + formData.append("file", file); + formData.append("boardId", boardId); + if ( newBoardName ){ + formData.append("newBoardName", newBoardName); + } + if ( siteUrl ){ + formData.append("siteUrl", siteUrl); + } + if ( description ){ + formData.append("description", description); + } + + let res = await fetch("./multiup", { + method: "POST", + body: formData + }); + + if ( res.status == 200 ){ + return res.json(); + } else { + console.error("error uploading status=" + res.status + " body=" + await res.text()); + throw new PostException(res.status); + } +} + function dispatchSocketConnect(){ window.dispatchEvent(new CustomEvent("socket-connect")); } @@ -121,7 +219,8 @@ let store = new Reef.Store({ previewImageUrl: null, siteUrl: "", description: "", - saveInProgress: false + saveInProgress: false, + didYouKnowDragAndDropMessageDisabled: window.localStorage.addPinModal_didYouKnowDragAndDropMessageDisabled == "true" || false }, pinZoomModal: { active: false, @@ -167,6 +266,23 @@ const appComponent = new Reef("#app", {
+ + ` //
} @@ -290,4 +406,50 @@ document.addEventListener("visibilitychange", async () => { } } -}); \ No newline at end of file +}); + +window.dragInProgress = false; + +window.ondragover = (evt) => { + + let data = store.data; + + if ( !data.board || data.addPinModal.active || data.editPinModal.active || data.aboutModal.active || data.pinZoomModal.active ){ + return; + } + + evt.preventDefault(); + + let hasFiles = event.dataTransfer.types.find(i => i == "Files") == "Files"; + if ( hasFiles ){ + window.dragInProgress = true; + document.getElementById("dragAndDropModal").classList.add("is-active"); + } +}; + +window.ondragleave = (evt) => { + if ( evt.x == 0 && evt.y == 0 ){ + document.getElementById("dragAndDropModal").classList.remove("is-active"); + window.dragInProgress = false; + } +} + +window.ondrop = async (evt) => { + + if ( window.dragInProgress ){ + evt.preventDefault(); + + document.getElementById("dragAndDropModal").classList.remove("is-active"); + + let hasFiles = event.dataTransfer.types.find(i => i == "Files") == "Files"; + if ( hasFiles ){ + store.do("app.uploadDroppedFiles", evt); + } + } + +}; + + + + + diff --git a/client/client.css b/client/client.css index 8ba820f..d8dc3e4 100644 --- a/client/client.css +++ b/client/client.css @@ -437,4 +437,15 @@ body.socketConnected #socketConnected { .is-touch .navbar.is-light .navbar-brand .navbar-link:hover, .is-touch .navbar.is-light .navbar-brand .navbar-link.is-active { background-color: transparent; +} + + +#dragAndDropModal .modal-background{ + background: #fff; +} + +#dragAndDropModal .box { + background: none; + border: 3px dashed #000; + text-align: center; } \ No newline at end of file diff --git a/client/components/addpin.js b/client/components/addpin.js index dd83ed9..d329b33 100644 --- a/client/components/addpin.js +++ b/client/components/addpin.js @@ -21,9 +21,18 @@ app.addSetter('addPinModal.close', (data) => { data.addPinModal.description = ""; data.addPinModal.newBoardName = ""; data.addPinModal.saveInProgress = false; + data.addPinModal.uploadFile = null; + + + // weird hack to pick up whether it redraws or not... + let fileInput = document.getElementById("fileInput"); + if ( fileInput ){ + fileInput.value = ""; + } }); app.addSetter('addPinModal.updatePreview', (data) => { + if ( data.addPinModal.imageUrl.startsWith("http") ){ ( async() => { let res = await fetch(data.addPinModal.imageUrl, { @@ -38,6 +47,7 @@ app.addSetter('addPinModal.updatePreview', (data) => { } else { data.addPinModal.previewImageUrl = null; } + }); app.addSetter('addPinModal.save', async (data) => { @@ -48,37 +58,63 @@ app.addSetter('addPinModal.save', async (data) => { let boardId = data.addPinModal.boardId; - let postData = { - boardId: boardId, - newBoardName: data.addPinModal.newBoardName, - imageUrl: data.addPinModal.imageUrl, - siteUrl: data.addPinModal.siteUrl, - description: data.addPinModal.description - }; - let res = await fetch('api/pins', { - method: 'POST', - headers: { - 'Content-Type': "application/json" - }, - body: JSON.stringify(postData) - }); - - if ( res.status == 200 ){ - - let body = await res.json(); - if ( data.board && data.board.id == boardId ){ - data.board.pins.push(body); + if ( data.addPinModal.uploadFile ){ + // do file upload + + console.log("attempting multipart file uploading"); + + try { + let newPin = await multipartUpload(data.addPinModal.uploadFile, boardId, data.addPinModal.newBoardName, data.addPinModal.siteUrl, data.addPinModal.description); + if ( data.board && data.board.id == boardId ){ + data.board.pins.push(newPin); + } + + window.localStorage.addPinLastBoardId = boardId; + store.do("addPinModal.close"); + + if ( boardId == "new" && !window.socketConnected ){ + store.do("load.boards"); + } + } catch (e){ + window.alert("Error uploading images."); + console.error("Error uploading images: ", e); } - window.localStorage.addPinLastBoardId = boardId; - store.do("addPinModal.close"); + } else { - // if we don't have a listening socket, we need to trigger our own update - if ( boardId == "new" && !window.socketConnected ){ - store.do("load.boards"); + let postData = { + boardId: boardId, + newBoardName: data.addPinModal.newBoardName, + imageUrl: data.addPinModal.imageUrl, + siteUrl: data.addPinModal.siteUrl, + description: data.addPinModal.description + }; + + let res = await fetch('api/pins', { + method: 'POST', + headers: { + 'Content-Type': "application/json" + }, + body: JSON.stringify(postData) + }); + + if ( res.status == 200 ){ + + let body = await res.json(); + if ( data.board && data.board.id == boardId ){ + data.board.pins.push(body); + } + + window.localStorage.addPinLastBoardId = boardId; + store.do("addPinModal.close"); + + // if we don't have a listening socket, we need to trigger our own update + if ( boardId == "new" && !window.socketConnected ){ + store.do("load.boards"); + } } - } + } store.do("loader.hide"); @@ -101,6 +137,54 @@ app.addGetter('addPinModal.isValid', (data) => { return true; }); +app.addSetter('addPinModal.fileChosen', (data, target) => { + + let file = target.files[0]; + + // check type + if ( file.type != "image/jpeg" && file.type != "image/png" ){ + + window.alert("Unsupported file type. JPEG and PNG images are supported."); + console.log("Unsupported file type: " + file.type); + + document.getElementById("fileInput").value = ""; + + } + + // check size + if ( file.size >= 26214400 ){ + window.alert("File size exceeds the 25MB limit."); + console.log("File size exceeds the 25MB limit. size=" + file.size); + document.getElementById("fileInput").value = ""; + } + + else { + + let imageUrl = window.URL.createObjectURL(file); + + data.addPinModal.uploadFile = file; + data.addPinModal.previewImageUrl = imageUrl; + } + +}); + +app.addSetter('addPinModal.removeUploadFile', (data, target) => { + data.addPinModal.uploadFile = null; + data.addPinModal.previewImageUrl = null; + return false; +}); + +app.addSetter('addPinModal.disableDidYouKnowDragAndDropMessage', (data) => { + data.addPinModal.didYouKnowDragAndDropMessageDisabled = true; + window.localStorage.addPinModal_didYouKnowDragAndDropMessageDisabled = "true"; +}); + +document.addEventListener("input", (evt) => { + if ( evt.target.id == "fileInput" ){ + store.do("addPinModal.fileChosen", evt.target); + } +}); + app.addComponent('addPinModal', (store) => { return new Reef("#addPinModal", { store: store, @@ -109,8 +193,11 @@ app.addComponent('addPinModal', (store) => { return new Reef("#addPinModal", { let imagePlaceholder = 'data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22300%22%20height%3D%22300%22%3E%3Crect%20x%3D%222%22%20y%3D%222%22%20width%3D%22300%22%20height%3D%22300%22%20style%3D%22fill%3A%23dedede%3B%22%2F%3E%3Ctext%20x%3D%2250%25%22%20y%3D%2250%25%22%20font-size%3D%2218%22%20text-anchor%3D%22middle%22%20alignment-baseline%3D%22middle%22%20font-family%3D%22monospace%2C%20sans-serif%22%20fill%3D%22%23555555%22%3Eimage%3C%2Ftext%3E%3C%2Fsvg%3E'; let options = ""; + for ( let i = 0; i < data.boards.length; ++i ){ - options += ``; + if ( data.showHiddenBoards || !data.boards[i].hidden ){ + options += ``; + } } let newBoardField = ''; @@ -134,6 +221,17 @@ app.addComponent('addPinModal', (store) => { return new Reef("#addPinModal", {