Hello all,
I'm trying to implement file upload feature on my Node-Red app. I used the flow shared by Nick O'Leary in an old GoogleForum post in this link.
The first solution that Nick shared actually works well but redirects dashboard page to a page displaying only OK. Nick also provided another solution with ajax calls to solve this issue but I could not make it work in my app. The reason that the fix didn't work for me, might have been caused because I used Dashboard 2.0.
How can I implement a file upload in Dashboard 2.0? I'm sharing the flow that worked with redirection below.
Thanks
[
{
"id": "61dfcbcdc7a29c9a",
"type": "ui-template",
"z": "c911c322aad92eb4",
"group": "e6e8fb22f71d7392",
"page": "",
"ui": "",
"name": "File Access",
"order": 0,
"width": 0,
"height": 0,
"head": "",
"format": "<template>\n\n<form action=\"/file-upload\" method=\"post\" enctype=\"multipart/form-data\">\n <input type=\"file\" name=\"fileToUpload\" id=\"fileToUpload\">\n <input type=\"submit\" value=\"Upload Image\" name=\"submit\">\n</form>\n\n</template>\n\n\n\n<script>\n\n \n\n export default {\n data() {\n // define variables available component-wide\n // (in <template> and component functions)\n return {\n videoFacingMode: 'user', // 'user' or 'environment'\n vConstraints: {\n audio: false,\n video: {\n facingMode: 'user'\n }\n }\n }\n },\n watch: {\n // watch for any changes of \"count\"\n \n },\n computed: {\n // automatically compute this variable\n // whenever VueJS deems appropriate\n \n },\n methods: {\n // expose a method to our <template> and Vue Application\n increase: function () {\n this.count++\n },\n takePhoto: function () {\n // played the sound\n //snap.currentTime = 0;\n //snap.play();\n \n // take the data out of the canvas\n const canvas = document.querySelector('.photo');\n const data = canvas.toDataURL('image/jpeg');\n const link = document.createElement('a');\n const strip = document.querySelector('.strip');\n const photoContainer = document.getElementById(\"photoContainer\");\n \n\n\n link.href = data;\n link.setAttribute('download', 'Picture');\n link.innerHTML = `<img src=\"${data}\" alt=\"The picture\" />`;\n strip.insertBefore(link, strip.firstChild);\n //click method downloads the image\n link.click();\n const arr = photoContainer.innerText;\n arr.push(data);\n photoContainer.innerText = arr;\n console.log(photoContainer);\n },\n takePhotoAndDownload: function(){\n // played the sound\n //snap.currentTime = 0;\n //snap.play();\n \n // take the data out of the canvas\n const canvas = document.querySelector('.photo');\n const data = canvas.toDataURL('image/jpeg');\n const link = document.createElement('a');\n const strip = document.querySelector('.strip');\n const photoArr = document.querySelector('.photoArr');\n \n link.href = data;\n link.download = \"testfilename.jpeg\";\n //click method downloads the image\n link.click();\n photoArr.push(data);\n }\n\n },\n mounted() {\n // code here when the component is first loaded\n\n const video = document.querySelector('.player');\n const canvas = document.querySelector('.photo');\n const ctx = canvas.getContext('2d');\n const strip = document.querySelector('.strip');\n const snap = document.querySelector('.snap');\n\n const photoContainer = document.createElement(\"photoContainer\");\n const arr = [];\n photoContainer.innerText = arr;\n console.log(photoContainer);\n\n // Fix for iOS Safari from https://leemartin.dev/hello-webrtc-on-safari-11-e8bcb5335295\n video.setAttribute('autoplay', '');\n video.setAttribute('muted', '');\n video.setAttribute('playsinline', '')\n\n // const constraints = {\n // audio: false,\n // video: {\n // facingMode: 'environment' // 'user' or 'environment'\n // }\n // }\n var facingMode = 'user';\n this.videoFacingMode = \"user\";\n \n\n var constraints = {\n audio: false,\n video: {\n facingMode: this.videoFacingMode//'environment' // 'user' or 'environment'\n }\n }\n\n\n video.addEventListener('click', function() {\n if (facingMode == \"user\") {\n facingMode = \"environment\";\n } else {\n facingMode = \"user\";\n }\n \n constraints = {\n audio: false,\n video: {\n facingMode: facingMode\n }\n }\n \n navigator.mediaDevices.getUserMedia(constraints).then(function success(stream) {\n video.srcObject = stream;\n });\n });\n\n\n function getVideo() {\n \n navigator.mediaDevices.getUserMedia(constraints)\n .then(localMediaStream => {\n console.log(localMediaStream);\n \n // DEPRECIATION : \n // The following has been depreceated by major browsers as of Chrome and Firefox.\n // video.src = window.URL.createObjectURL(localMediaStream);\n // Please refer to these:\n // Deprecated - https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL\n // Newer Syntax - https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/srcObject\n console.dir(video);\n if ('srcObject' in video) {\n video.srcObject = localMediaStream;\n } else {\n video.src = URL.createObjectURL(localMediaStream);\n }\n // video.src = window.URL.createObjectURL(localMediaStream);\n video.play();\n })\n .catch(err => {\n console.error(`OH NO!!!!`, err);\n });\n }\n\n function paintToCanvas() {\n const width = video.videoWidth;\n const height = video.videoHeight;\n canvas.width = width;\n canvas.height = height;\n\n return setInterval(() => {\n ctx.drawImage(video, 0, 0, width, height);\n // take the pixels out\n // let pixels = ctx.getImageData(0, 0, width, height);\n // mess with them\n // pixels = redEffect(pixels);\n\n // pixels = rgbSplit(pixels);\n // ctx.globalAlpha = 0.8;\n\n // pixels = greenScreen(pixels);\n // put them back\n // ctx.putImageData(pixels, 0, 0);\n }, 16);\n }\n\n \n\n function redEffect(pixels) {\n for (let i = 0; i < pixels.data.length; i+=4) {\n pixels.data[i + 0] = pixels.data[i + 0] + 200; // RED\n pixels.data[i + 1] = pixels.data[i + 1] - 50; // GREEN\n pixels.data[i + 2] = pixels.data[i + 2] * 0.5; // Blue\n }\n return pixels;\n }\n\n function rgbSplit(pixels) {\n for (let i = 0; i < pixels.data.length; i+=4) {\n pixels.data[i - 150] = pixels.data[i + 0]; // RED\n pixels.data[i + 500] = pixels.data[i + 1]; // GREEN\n pixels.data[i - 550] = pixels.data[i + 2]; // Blue\n }\n return pixels;\n }\n\n function greenScreen(pixels) {\n const levels = {};\n\n document.querySelectorAll('.rgb input').forEach((input) => {\n levels[input.name] = input.value;\n });\n\n for (i = 0; i < pixels.data.length; i = i + 4) {\n red = pixels.data[i + 0];\n green = pixels.data[i + 1];\n blue = pixels.data[i + 2];\n alpha = pixels.data[i + 3];\n\n if (red >= levels.rmin\n && green >= levels.gmin\n && blue >= levels.bmin\n && red <= levels.rmax\n && green <= levels.gmax\n && blue <= levels.bmax) {\n // take it out!\n pixels.data[i + 3] = 0;\n }\n }\n\n return pixels;\n }\n\n function takePhoto() {\n // played the sound\n //snap.currentTime = 0;\n //snap.play();\n \n // take the data out of the canvas\n const data = canvas.toDataURL('image/jpeg');\n const link = document.createElement('a');\n link.href = data;\n link.setAttribute('download', 'picture');\n link.innerHTML = `<img src=\"${data}\" alt=\"The Picture\" />`;\n strip.insertBefore(link, strip.firstChild);\n }\n \n \n //getVideo();\n\n //video.addEventListener('canplay', paintToCanvas);\n },\n unmounted() {\n // code here when the component is removed from the Dashboard\n // i.e. when the user navigates away from the page\n }\n\n \n }\n</script>\n<style>\n /* define any styles here - supports raw CSS */\n .my-class {\n color: red;\n }\n html {\n box-sizing: border-box;\n }\n \n *, *:before, *:after {\n box-sizing: inherit;\n }\n \n html {\n font-size: 10px;\n background: #ff0000;\n }\n \n .photobooth {\n background: white;\n max-width: 150rem;\n margin: 2rem auto;\n border-radius: 2px;\n }\n \n /*clearfix*/\n .photobooth:after {\n content: '';\n display: block;\n clear: both;\n }\n \n .photo {\n width: 100%;\n float: left;\n }\n \n .player {\n position: absolute;\n top: 20px;\n right: 20px;\n width:200px;\n }\n \n /*\n Strip!\n */\n \n .strip {\n padding: 2rem;\n }\n \n .strip img {\n width: 100px;\n overflow-x: scroll;\n padding: 0.8rem 0.8rem 2.5rem 0.8rem;\n box-shadow: 0 0 3px rgba(0,0,0,0.2);\n background: white;\n }\n \n .strip a:nth-child(5n+1) img { transform: rotate(10deg); }\n .strip a:nth-child(5n+2) img { transform: rotate(-2deg); }\n .strip a:nth-child(5n+3) img { transform: rotate(8deg); }\n .strip a:nth-child(5n+4) img { transform: rotate(-11deg); }\n .strip a:nth-child(5n+5) img { transform: rotate(12deg); }\n</style>",
"storeOutMessages": true,
"passthru": true,
"resendOnRefresh": true,
"templateScope": "local",
"className": "",
"x": 290,
"y": 860,
"wires": [
[]
]
},
{
"id": "90de0cfe.ca90a",
"type": "http in",
"z": "c911c322aad92eb4",
"name": "",
"url": "/file-upload",
"method": "post",
"upload": true,
"swaggerDoc": "",
"x": 314,
"y": 932,
"wires": [
[
"f44eee6a.fa1a8",
"7835838e.a259cc"
]
]
},
{
"id": "f44eee6a.fa1a8",
"type": "debug",
"z": "c911c322aad92eb4",
"name": "",
"active": true,
"console": "false",
"complete": "req.files",
"x": 630,
"y": 940,
"wires": []
},
{
"id": "7835838e.a259cc",
"type": "function",
"z": "c911c322aad92eb4",
"name": "",
"func": "msg.payload = \"OK\";\nreturn msg;",
"outputs": 1,
"noerr": 0,
"x": 421,
"y": 984,
"wires": [
[
"b2930be5.501e38"
]
]
},
{
"id": "b2930be5.501e38",
"type": "http response",
"z": "c911c322aad92eb4",
"name": "",
"statusCode": "",
"headers": {},
"x": 521,
"y": 1024,
"wires": []
},
{
"id": "e6e8fb22f71d7392",
"type": "ui-group",
"name": "My Group",
"page": "e2669f7ebdf6c137",
"width": "6",
"height": "1",
"order": -1,
"showTitle": true,
"className": "",
"visible": "true",
"disabled": "false"
},
{
"id": "e2669f7ebdf6c137",
"type": "ui-page",
"name": "Page N",
"ui": "cf18f162c725eabc",
"path": "/pageN",
"icon": "home",
"layout": "grid",
"theme": "f33b4cc2097266ae",
"order": -1,
"className": "",
"visible": "true",
"disabled": "false"
},
{
"id": "cf18f162c725eabc",
"type": "ui-base",
"name": "My Dashboard1",
"path": "/dashboard",
"includeClientData": true,
"acceptsClientConfig": [
"ui-notification",
"ui-control"
],
"showPathInSidebar": false,
"navigationStyle": "default"
},
{
"id": "f33b4cc2097266ae",
"type": "ui-theme",
"name": "Default Theme",
"colors": {
"surface": "#ffffff",
"primary": "#0094CE",
"bgPage": "#eeeeee",
"groupBg": "#ffffff",
"groupOutline": "#cccccc"
},
"sizes": {
"pagePadding": "12px",
"groupGap": "12px",
"groupBorderRadius": "4px",
"widgetGap": "12px"
}
}
]