I would like some help to adjust an upload script, my goal is for the script to detect in which folder a file should be uploaded.
I created an HTML form in a Google Web App, this is my HTML form:
This form has 2 files, the .gs file has this code here:
function doGet(e) {
return HtmlService.createTemplateFromFile('forms0101.html').evaluate();
}
function getOAuthToken() {
return ScriptApp.getOAuthToken();
}
function getParent(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var id = ss.getId();
var parent = DriveApp.getFileById(id).getParents().next().getId();
return parent
}
function getLimitFolder(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var pastapai = DriveApp.getFileById(ss.getId()).getParents();
var limitfolder = pastapai.next().getFoldersByName("_").next().getId();
return limitfolder
}
const saveDataAsCSV = (data, folderId) => DriveApp.getFolderById(folderId).createFile("sample.csv", data);
/**
* creates a folder under a parent folder, and returns it's id. If the folder already exists
* then it is not created and it simply returns the id of the existing one
*/
function createOrGetFolder(folderName, parentFolderId) {
try {
var parentFolder = DriveApp.getFolderById(parentFolderId),
folder;
if (parentFolder) {
var foldersIter = parentFolder.getFoldersByName("Video");
if (foldersIter.hasNext()) {
video_folder = foldersIter.next().getFoldersByName(folderName); //folderName esta definido no arquivo forms.html
if (video_folder.hasNext()) {
folder = video_folder.next();
}
} else {
folder = parentFolder.createFolder("Video");
folder = folder.createFolder(folderName);
}
} else {
throw new Error("Parent Folder with id: " + parentFolderId + " not found");
}
return folder.getId();
} catch (error) {
return error;
}
}
// NOTE: always make sure we use DriveApp, even if it's in a comment, for google to import those
// libraries and allow the rest of the app to work. see https://github.com/tanaikech/Resumable_Upload_For_WebApps
The HTML file has exactly this code here:
<!DOCTYPE html>
<html>
<head>
<base target="_blank">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Drive Multi Large File Upload</title>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.5/css/materialize.min.css">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<style>
#import url('https://fonts.googleapis.com/css2?family=Rubik:wght#400;600;700&display=swap');
.disclaimer{
width: 480px;
color: #646464;
margin: 20px auto;
padding:0 16px;
text-align:center;
font:400 12px Rubik,sans-serif;
}
h5.center-align.teal-text {
font:700 26px Rubik,sans-serif;
color: #00F498!important;
}
.row {
font:600 14px Rubik,sans-serif;
}
.btn {
background-color: black;
}
.btn:hover {
background-color: #00F498;
}
body {
margin-top: -40px;
}
#progress {
color: #00000;
}
.disclaimer a{
color: #00BCAA;
}
#credit{
display:none
}
</style>
</head>
<body>
<form class="main" id="form" novalidate="novalidate" style="max-width: 480px;margin: 40px auto;">
<div id="forminner">
<h5 class="center-align teal-text" style="margin-bottom: -10px; font-size: 20px; font-family: Rubik; ">YOUR NAME</h5>
<div class="row">
<div class="input-field col s12">
<input id="name01" type="text" name="Name" class="validate" required="required" aria-required="true">
<label for="name" class="">Name</label>
</div>
</div>
<h5 class="center-align teal-text" style="margin-bottom: -10px; font-size: 20px; font-family: Rubik; ">SOME DESCRIPTION</h5>
<div class="row">
<div class="input-field col s12">
<input id="description" type="text" name="Description" class="validate" required="required" aria-required="true">
<label for="name">Description</label>
</div>
</div>
<div class="row">
<div class="col-8 col-md-4">
<h6>Model</h6>
<select class="custom-select" id="Model">
<option selected="">Choose...</option>
<option value="01">01</option>
<option value="02">02</option>
<option value="03">03</option>
</select>
<h6>Color</h6>
<select class="custom-select" id="Color">
<option selected="">Choose...</option>
<option value="Red">Red</option>
<option value="Green">Green</option>
</select>
</div>
</div>
<div class="row">
<div class="col s12">
<h5 class="center-align teal-text">Upload the Video File</h5>
</div>
</div>
<div class="row">
<div class="file-field input-field col s12">
<div id="input-btn" class="btn">
<span>File</span>
<input id="files" type="file" single="">
</div>
<div class="file-path-wrapper">
<input class="file-path validate" type="text" placeholder="Select the file">
</div>
</div>
</div>
<div class="row">
<div class="input-field col s6">
<button id="submit-btn" class="waves-effect waves-light btn submit-btn" type="submit" onclick="submitForm(); return false;">Submit</button>
</div>
</div>
<div class="row">
<div class="input-field col s12 hide" id="update">
<hr>
<p>
Por favor, aguarde enquanto seu arquivo está sendo carregado.<br><span style="color: #00000;"><b>Não feche ou atualize a janela durante o upload.</b></span>
</p>
</div>
</div>
<div class="row">
<div class="input-field col s12" id="progress">
</div>
</div>
</div>
</div>
<div id="success" style="display:none">
<h5 class="center-align teal-text">Tudo certo!</h5>
<p>Se você já preencheu todos os campos é só fechar essa janela e clicar em enviar!</p>
<button id="fechar" class="waves-effect waves-light btn submit-btn" style ="transform: translateX(160%);" type="button" onclick="google.script.host.close()">Fechar</button>
</div>
</form>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.5/js/materialize.min.js"></script>
<script src="https://gumroad.com/js/gumroad.js"></script>
<script>
var upload_folder = "01-01"
const chunkSize = 5242880;
const uploadParentFolderId = <?=getParent()?>; // creates a folder inside of this folder
const limitfolder = <?=getLimitFolder()?>;
function closer(){
google.script.host.close();
}
function submitForm() {
// Added the below script.
if ($('#submit-btn.disabled')[0]) return; // short circuit
var name = upload_folder
var files = [...$('#files')[0].files]; // convert from FileList to array
if (files.length === 0) {
showError("Por favor, selecione um arquivo");
return;
}
var name = $('#name01').val();
var description = $('#description').val();
var model = $('#Model').val();
var color = $('#Color').val();
var form_values = [name, description, model, color];
form_values = form_values.map(r => r.replaceAll(",", "#")); // Essa linha substitui todas as "," por "#" antes de gerar o .csv
var data = form_values.join(",");
google.script.run.saveDataAsCSV(data, uploadParentFolderId);
google.script.run.saveDataAsCSV(data, limitfolder);
disableForm(); // prevent re submission
// the map and reduce are here to ensure that only one file is uploaded at a time. This allows
// the promises to be run sequentially
files.map(file => uploadFilePromiseFactory(file))
.reduce((promiseChain, currentTask) => {
return promiseChain.then(currentTask);
}, Promise.resolve([])).then( () => {
console.log("Completed all files upload");
showSuccess();
});
}
function disableForm() {
$('#submit-btn').addClass('disabled');
$('#input-btn').addClass('disabled');
$('#update').removeClass('hide');
$('#update').removeClass('hide');
}
function uploadFilePromiseFactory(file) {
return () => {
console.log("Processing: ", file.name);
return new Promise((resolve, reject) => {
showProgressMessage("Seu arquivo está sendo carregado");
var fr = new FileReader();
fr.fileName = file.name;
fr.fileSize = file.size;
fr.fileType = file.type;
// not sure of a better way of passing the promise functions down
fr.resolve = () => resolve();
fr.reject = (error) => reject(error);
fr.onload = onFileReaderLoad;
fr.readAsArrayBuffer(file);
});
};
}
/**
* Gets called once the browser has loaded a file. The main logic that creates a folder
* and initiates the file upload resides here
*/
function onFileReaderLoad(onLoadEvent) {
var fr = this;
var newFolderName = upload_folder
createOrGetFolder(newFolderName, uploadParentFolderId).then(newFolderId => {
console.log("Found or created guest folder with id: ", newFolderId);
uploadFileToDriveFolder.call(fr, newFolderId).then(() => {
fr.resolve();
}, (error) => {
fr.reject(error);
});
},
(error) => {
if (error) {
showError(error.toString());
}
console.log("onFileReaderLoad Error2: ", error);
});
}
/**
* call to the DriveApp api. Wrapped in a promise in case I want to address timing issues between a
* createFolder and findFolderById
*/
function createOrGetFolder(folderName, parentFolderId) {
return new Promise((resolve, reject) => {
google.script.run.withSuccessHandler(response => {
console.log("createOrGetFolder response: ", response);
if (response && response.length) {
resolve(response);
}
reject(response);
}).createOrGetFolder(folderName, parentFolderId);
});
}
/**
* Helper functions modified from:
* https://github.com/tanaikech/Resumable_Upload_For_WebApps
*/
function uploadFileToDriveFolder(parentFolderId) {
var fr = this;
return new Promise((resolve, reject) => {
var fileName = fr.fileName;
var fileSize = fr.fileSize;
var fileType = fr.fileType;
console.log({fileName: fileName, fileSize: fileSize, fileType: fileType});
var buf = fr.result;
var chunkpot = getChunkpot(chunkSize, fileSize);
var uint8Array = new Uint8Array(buf);
var chunks = chunkpot.chunks.map(function(e) {
return {
data: uint8Array.slice(e.startByte, e.endByte + 1),
length: e.numByte,
range: "bytes " + e.startByte + "-" + e.endByte + "/" + chunkpot.total,
};
});
google.script.run.withSuccessHandler(oAuthToken => {
var xhr = new XMLHttpRequest();
xhr.open("POST", "https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable");
xhr.setRequestHeader('Authorization', "Bearer " + oAuthToken);
xhr.setRequestHeader('Content-Type', "application/json");
xhr.send(JSON.stringify({
mimeType: fileType,
name: fileName,
parents: [parentFolderId]
}));
xhr.onload = () => {
doUpload(fileName, {
location: xhr.getResponseHeader("location"),
chunks: chunks,
}).then(success => {
resolve(success);
console.log("Successfully uploaded: ", fileName);
},
error => {
reject(error);
});
};
xhr.onerror = () => {
console.log("ERROR: ", xhr.response);
reject(xhr.response);
};
}).getOAuthToken();
});
}
function showSuccess() {
$('#forminner').hide();
$('#success').show();
$('#fechar').show();
}
function showError(e) {
$('#progress').addClass('red-text').html(e);
}
function showMessage(e) {
$('#update').html(e);
}
function showProgressMessage(e) {
$('#progress').removeClass('red-text').html(e);
}
/**
* Helper functions modified from:
* https://github.com/tanaikech/Resumable_Upload_For_WebApps
*/
function doUpload(fileName, e) {
return new Promise((resolve, reject) => {
showProgressMessage("Carregando: <span style='color: #00F498 ;'>" + "0%</span>");
var chunks = e.chunks;
var location = e.location;
var cnt = 0;
var end = chunks.length;
var temp = function callback(cnt) {
var e = chunks[cnt];
var xhr = new XMLHttpRequest();
xhr.open("PUT", location, true);
console.log("content range: ", e.range);
xhr.setRequestHeader('Content-Range', e.range);
xhr.send(e.data);
xhr.onloadend = function() {
var status = xhr.status;
cnt += 1;
console.log("Uploading: " + status + " (" + cnt + " / " + end + ")");
showProgressMessage("Carregando: <span style='color: #00F498 ;'>"
+ Math.floor(100 * cnt / end) + "%</span>" );
if (status == 308) {
callback(cnt);
} else if (status == 200) {
$("#progress").text("Done.");
resolve();
} else {
$("#progress").text("Error: " + xhr.response);
reject();
}
};
}(cnt);
});
}
/**
* Helper functions modified from:
* https://github.com/tanaikech/Resumable_Upload_For_WebApps
*/
function getChunkpot(chunkSize, fileSize) {
var chunkPot = {};
chunkPot.total = fileSize;
chunkPot.chunks = [];
if (fileSize > chunkSize) {
var numE = chunkSize;
var endS = function(f, n) {
var c = f % n;
if (c == 0) {
return 0;
} else {
return c;
}
}(fileSize, numE);
var repeat = Math.floor(fileSize / numE);
for (var i = 0; i <= repeat; i++) {
var startAddress = i * numE;
var c = {};
c.startByte = startAddress;
if (i < repeat) {
c.endByte = startAddress + numE - 1;
c.numByte = numE;
chunkPot.chunks.push(c);
} else if (i == repeat && endS > 0) {
c.endByte = startAddress + endS - 1;
c.numByte = endS;
chunkPot.chunks.push(c);
}
}
} else {
var chunk = {
startByte: 0,
endByte: fileSize - 1,
numByte: fileSize,
};
chunkPot.chunks.push(chunk);
}
return chunkPot;
}
</script>
</body>
</html>
As you can see, currently the upload folder is hardcoded in the script and is defined in the variable var upload_folder = "01-01".
Taking a look at my form, notice that I have a field called Model, this field is important to define the model of the client's request.
The issue is that each folder should only contain a single file, so I created several upload folders following this pattern:
01-01, 01-02, 01-03, 02-01, 02-02, 02-03...
The first number refers to the model selected on the form, the second number refers to the slot used.
What I need is that, according to the model selected in the form, the script checks which folder referring to that model does not yet have a file and uploads the video file to that folder.
Exemplifying
Model 01 was selected in the form
The script checks if folder 01-01 has a file, if it does, it uploads the file to folder 01-02, if that is also busy, it uploads the file to folder 01-03.
Model 02 was selected in the form
The script checks if folder 02-01 has a file, if it does, it uploads the file to folder 02-02, if that is also busy, it uploads the file to folder 02-03.
I'm a beginner in JavaScript, so I have some difficulty with this type of operation involving Client and Server... besides, I couldn't think of any way to get to this result without filling the code with IF/Else.
I would be very grateful with any help.
EDIT
After Tanaike's suggestion, the script is close to working as expected, the issue is that for some reason the first folder is generated with an extra -01 looking like this:
Another point is that when I change the option in the form, for example 02, the folder taken into account continues to be 01, that is, even if 02 is selected in the form, the script uploads the file to the next empty folder 01.
I tried to modify the variable var upload_folder = "01-01"; to var upload_folder = $('#Model').val(); but this way the folder created is Choose...-01, I believe this happens because it would be necessary to return the information from $('#Model').val() to the .gs file somehow.
I would also like that when the third folder of the selected model is exceeded, the script displays an error popup instead of continuing to create more folders, that is, if the option 01 is selected in the form's model field and folders 01-01, 01-02, 01-03 are full, it is not to create a 01-04 but to display a message like Wait for model 01 release.
I've made the upload folder access free so you can see exactly what's going on:
Stack Thread - Demonstration Folder
From your following expected result,
each folder should only contain a single file
The script checks if folder 01-01 has a file, if it does, it uploads the file to folder 01-02, if that is also busy, it uploads the file to folder 01-03.
The script checks if folder 02-01 has a file, if it does, it uploads the file to folder 02-02, if that is also busy, it uploads the file to folder 02-03.
How about modifying your script as follows?
Modified script:
In this modification, your function createOrGetFolder is modified.
function createOrGetFolder(folderName, parentFolderId) {
try {
var parentFolder = DriveApp.getFolderById(parentFolderId), folder;
if (parentFolder) {
var foldersIter = parentFolder.getFoldersByName("Video");
if (foldersIter.hasNext()) {
var videoFolder = foldersIter.next();
var nextFolderName = folderName + "-01";
while (!folder) {
video_folder = videoFolder.getFoldersByName(nextFolderName);
if (video_folder.hasNext()) {
folder = video_folder.next();
var files = folder.getFiles();
if (files.hasNext()) {
var [a, b] = nextFolderName.split("-");
nextFolderName = `${a}-${String(Number(b) + 1).padStart(2, "0")}`;
folder = null;
}
} else {
folder = videoFolder.createFolder(nextFolderName);
}
}
} else {
folder = parentFolder.createFolder("Video");
folder = folder.createFolder(folderName);
}
} else {
throw new Error("Parent Folder with id: " + parentFolderId + " not found");
}
return folder.getId();
} catch (error) {
return error;
}
}
When I saw your HTML, for example, when 02 is selected from "Model", the value of it is 02. So, in this modification, in order to scan the folders and files in each folder, I used 02-01 as the 1st search folder.
By this modification, for example, when folderName is 02, the folder names of 02-01, 02-02,,, are searched. When the files are not found in the folder, the folder ID is returned.
Note:
When you modified the Google Apps Script of Web Apps, please modify the deployment as a new version. By this, the modified script is reflected in Web Apps. Please be careful about this.
You can see the detail of this in my report "Redeploying Web Apps without Changing URL of Web Apps for new IDE (Author: me)".
Added:
About 01-01-01 folder, in this case, it is considered that in your javascript, you are retrieving the value from the dropdown list with var model = $('#Model').val(); (in this case, the values are 01, 02, 03,,,), but, as the default folder name, you are using var upload_folder = "01-01". I think that this is the reason for your new 1st issue. So, please modify var upload_folder = "01-01" to var upload_folder = "01".
And, in your javascript, by var newFolderName = upload_folder, the same folder name of upload_folder is always used. I think that this is the reason for your new 2nd issue. So, please modify them as follows.
From:
var upload_folder = "01-01"
To:
var upload_folder = "01";
And,
From:
var name = $('#name01').val();
var description = $('#description').val();
var model = $('#Model').val();
var color = $('#Color').val();
To:
var name = $('#name01').val();
var description = $('#description').val();
var model = $('#Model').val();
upload_folder = model;
var color = $('#Color').val();
Related
I am working on a django app in which the html code called tool.hmtl along with the javascript code called myscripts.js let the user upload the folder and then do some processing on that data. Relevant section of tool.hmtl is given as follows.
<main class="tool mg-t-900">
<div id="folderUploadDiv">
<h1 class="tool__heading | text-center text-capitalize">
Upload your folder here
</h1>
<label
for="folder"
class="tool__upload | flex flex-center flex-column mg-t-500 text-center"
>
<i class="ri-upload-cloud-line tool__icon"></i>
<p class="tool__txt text-capitalize">Browse folder to upload</p>
</label>
<input type="file" id="folder" webkitdirectory multiple />
.............some code......
<script src="{% static 'myscripts.js' %}"></script>
<script src="{% static 'app.js' %}"></script>
<script>
const fileInput = document.querySelector("#folder");
const loder = document.querySelector(".loder");
const toggleLoder = function () {
loder.classList.add("active");
setTimeout(() => {
loder.classList.remove("active");
}, 30000);
startTypingAnimation();
};
fileInput.addEventListener("change", toggleLoder);
function startTypingAnimation() {
new TypeIt("#loder-text", {
strings: "Your file is being prepared...",
speed: 75,
loop: true,
}).go();
}
</script>
<script>
setTimeout(function () {
location.reload();
}, 300000); // refresh every 5 minutes (300000 milliseconds)
</script>
</body>
</html>
{% endblock %}
and relevant section of myscripts.js is as under.
// const axios = require('axios');
$("#folderUploadDiv").show();
$("#selectorDiv").hide();
username = "";
contentType = "";
var dataToSend = []
var flag=1
document.getElementById("folder").addEventListener("change", function(event) {
var output = document.querySelector("ul");
var files = event.target.files;
var jsonFiles =0;
var rightOrderFiles = [];
for (var i=0; i<files.length; i++) {
var item = document.createElement("li");
var innerFiles = files[i].webkitRelativePath.split("/");
if(innerFiles.length ===4){
jsonFiles++;
rightOrderFiles.push(files[i].webkitRelativePath)
var reader = new FileReader();
reader.onload = onReaderLoad;
reader.readAsText(files[i]);
}
// console.log("file path : ", files[i].webkitRelativePath);
item.innerHTML = files[i].webkitRelativePath;
output.appendChild(item);
};
}, false);
In this code snippets (both HTML and Javascript), we can see that user uploads the folder and then some processing is done in Javascript.
My question is, how can I change the code both in tool.hmtl along with the javascript code myscripts.js such that the user uploads a zip file and a folder is extracted from that zip file rather than user uploading the folder itself. Everything else remain the same, the only change needed is let the user upload zip file instead of the folder directly and the folder is then extracted from the zip file.
To change the code to extract a folder from a zip file, you need to make the following changes in both tool.html and myscripts.js:
1- In tool.html, change the type of the file input to "file" instead of "folder".
<input type="file" id="folder" />
2- In myscripts.js, use a library such as jszip.js to extract the contents of the uploaded zip file. You can add the library to your project by including the following line in the head of tool.html:
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.2.2/jszip.min.js"></script>
3- Replace the event listener for the file input with the following code:
document.getElementById("folder").addEventListener("change", function(event) {
var output = document.querySelector("ul");
var file = event.target.files[0];
var reader = new FileReader();
reader.onload = function (e) {
var zip = new JSZip();
zip.loadAsync(e.target.result)
.then(function (zip) {
var rightOrderFiles = [];
var jsonFiles = 0;
zip.forEach(function (relativePath, file) {
if (relativePath.split("/").length === 4) {
jsonFiles++;
rightOrderFiles.push(relativePath);
file.async("text").then(function (content) {
var item = document.createElement("li");
item.innerHTML = relativePath;
output.appendChild(item);
});
}
});
});
};
reader.readAsArrayBuffer(file);
}, false);
This code uses JSZip to load the contents of the uploaded zip file, extract the files and process them in the same manner as before.
I am using a script based on this one to allow users to add multiple images to a form.
The form has multiple other inputs and the multi-image selection is part of it.
If I select files 'manually' using the 'click here' then the files are sent with the form submit. If the user drops them onto the 'dropzone' however, they do not get sent with the form. How can I make sure they do?
let dropBox = document.getElementById('dropBox');
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(evt => {
dropBox.addEventListener(evt, prevDefault, false);
});
function prevDefault(e) {
e.preventDefault();
e.stopPropagation();
}
['dragenter', 'dragover'].forEach(evt => {
dropBox.addEventListener(evt, hover, false);
});
['dragleave', 'drop'].forEach(evt => {
dropBox.addEventListener(evt, unhover, false);
});
function hover(e) {
dropBox.classList.add('!bg-lime-100', 'border-lime-300');
}
function unhover(e) {
dropBox.classList.remove('!bg-lime-100', 'border-lime-300');
}
dropBox.addEventListener('drop', mngDrop, false);
function mngDrop(e) {
let dataTrans = e.dataTransfer;
let files = dataTrans.files;
filesManager(files);
}
function filesManager(files) {
files = [...files];
files.forEach(previewFile);
}
function previewFile(file) {
if (!file.type.match(/image.*/)) {
return alert('Only JPG, JPEG and PNG files are allowed');
}
let fReader = new FileReader();
let gallery = document.getElementById('gallery');
fReader.readAsDataURL(file);
fReader.onloadend = function() {
let wrap = document.createElement('div');
let img = document.createElement('img');
img.src = fReader.result;
img.classList.add('rounded-lg');
let imgCapt = document.createElement('p');
let fSize = Math.round((file.size / 1024 / 1024) * 10) / 10 + ' MB';
imgCapt.innerHTML = `<span class="text-md font-bold tracking-tight">${fSize}</span>`;
gallery.appendChild(wrap).appendChild(img);
gallery.appendChild(wrap).appendChild(imgCapt);
}
}
<form method="post" action="..." enctype="multipart/form-data">
<input type="text" name="name">
<input type="text" name="example">
<input type="text" name="etc">
<div id="dropBox" class="border-2 border-dashed bg-white border-slate-300 rounded-lg md:p-5 p-10 text-center">
<p class="mb-2 mt-4">Drag and drop your images here, or</p>
<input type="file" name="photos[]" id="imgUpload" multiple accept="image/*" onchange="filesManager(this.files)" class="hidden">
<label for="imgUpload" class="block mt-4 font-bold cursor-pointer"><u>Click here</u> to select from your computer</label>
<div id="gallery" class="mt-4 grid grid-cols-2 md:grid-cols-3 gap-4"></div>
</div>
</form>
I tried adding this in the previewFile function but that doesn't work:
let dropBox = document.getElementById('dropBox');
let formData = new FormData(dropBox[0]);
formData.append('photos', file);
<input type="file"> elements are read-only inputs. Files can't be assigned to them. For security reasons user input is necessary to attach a file on the client computer to a form. The "Browse..." or "Choose file..." button provides that user input.
In the case of a drag and drop interaction create a FormData object:
const photoFormData = new FormData();
...and append() each file to it using forEach():
files.forEach((file, idx) => {
photoFormData.append(`photos[${idx}]`, file);
});
The index (idx) of the file in the files array is used to differentiate each file in the FormData object.
When the user submits the form, use a submit addEventListener() to post the FormData object holding the files to the location the form is posted to using fetch():
document.querySelector('form').addEventListener('submit', function ({target}) {
fetch(target.action, {
method: 'put',
body: photoFormData
})
.then(result => { console.log('Success:', result); })
.catch(error => { console.error('Error:', error); });
});
Appending the files can happen in the filesManager() function:
function filesManager(files) {
files = [...files];
files.forEach((file, idx) => {
photoFormData.append(`photos[${idx}]`, file);
});
files.forEach(previewFile);
}
let dropBox = document.getElementById('dropBox');
const photoFormData = new FormData();
document.querySelector('form').addEventListener('submit', function ({target}) {
fetch(target.action, {
method: 'put',
body: photoFormData
})
.then(result => { console.log('Success:', result); })
.catch(error => { console.error('Error:', error); });
});
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(evt => {
dropBox.addEventListener(evt, prevDefault, false);
});
function prevDefault(e) {
e.preventDefault();
e.stopPropagation();
}
['dragenter', 'dragover'].forEach(evt => {
dropBox.addEventListener(evt, hover, false);
});
['dragleave', 'drop'].forEach(evt => {
dropBox.addEventListener(evt, unhover, false);
});
function hover(e) {
dropBox.classList.add('!bg-lime-100', 'border-lime-300');
}
function unhover(e) {
dropBox.classList.remove('!bg-lime-100', 'border-lime-300');
}
dropBox.addEventListener('drop', mngDrop, false);
function mngDrop(e) {
let dataTrans = e.dataTransfer;
let files = dataTrans.files;
filesManager(files);
}
function filesManager(files) {
files = [...files];
files.forEach((file, idx) => {
photoFormData.append(`photos[${idx}]`, file);
});
files.forEach(previewFile);
}
function previewFile(file) {
if (!file.type.match(/image.*/)) {
return alert('Only JPG, JPEG and PNG files are allowed');
}
let fReader = new FileReader();
let gallery = document.getElementById('gallery');
fReader.readAsDataURL(file);
fReader.onloadend = function() {
let wrap = document.createElement('div');
let img = document.createElement('img');
img.src = fReader.result;
img.classList.add('rounded-lg');
let imgCapt = document.createElement('p');
let fSize = Math.round((file.size / 1024 / 1024) * 10) / 10 + ' MB';
imgCapt.innerHTML = `<span class="text-md font-bold tracking-tight">${fSize}</span>`;
gallery.appendChild(wrap).appendChild(img);
gallery.appendChild(wrap).appendChild(imgCapt);
}
}
So I have a piece of code that automatically changes a .psd image depending on the user inputs.
I am using https://www.photopea.com/ to achieve this using their handy API and scripts.
Note: Photopea has a nice feature that when you enter your url inside the server array, and you call save as a script, it saves the file, posted to your server link you put. This works fine (Only when I have the page opened myself like a normal client. Not without opening the page).
My issue is, I need to be able to make a post request to update the script (to the iframe window), without the client actually opening the page / browser. This is sort like an API. It needs to check an incoming post request for the values entered to change on the .psd, then it needs to automatically do these changes without the user needing to open the page.
Sorta like an api running in the background, checking for incoming connections, then once it has found the post request to our node js, it will run the script to change the .psd layers to suit users values.
What I'm stuck at:
When I run the script in a API sorta mode (through post request, not opening the page manually, it shows me this error message here.
This is my html page I need to load without needing the client to open it up:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>PSD Changer</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js" integrity="sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<style type="text/css">
input {
padding: 7px;
width: 100%;
margin: 10px 0px 10px 0px;
}
</style>
</head>
<body>
<textarea rows="8", style="width:100%;" id="json">
{
"files": [
"https://www.photopea.com/#MY_PHOTOPEA_PSD"
],
"resources" : [
],
"environment" : {
},
"server" : {
"version" : 1,
"url" : "https://www.myserver.cc/saveFile.php",
"formats" : [ "png:true", "jpg:0.5" ]
}
}</textarea>
<button onclick="updatePlayground()" style="display:block; font-size:1.2em;" >Update</button> <br />
<b>URL:</b>
<textarea rows="3", style="width:100%;" id="url"></textarea>
<iframe src="https://www.photopea.com?rnd=44567643" id="pp" style="border:none; width:100%; height:600px;"></iframe>
<button onclick="testOurAuto();">Auto Change Text</button>
<h2>Run a script</h2>
<textarea rows="4", style="width:100%;" id="script">app.activeDocument.activeLayer.translate(20,20);</textarea>
<button onclick="runScript()" style="display:block; font-size:1.2em;">Run</button> <br />
<b>Messages from Photopea</b>
<pre id="msgs"></pre>
<hr>
<div style="background: black; margin-top: 50px; color: white; padding: 100px;">
<h1>Auto Config Editor</h1>
<label>Change first name to:</label>
<input type="text" id="new_first_name" placeholder="Enter new first name for ID here" />
<br>
<label>Change last name to:</label>
<input type="text" id="new_last_name" placeholder="Enter new last name for ID here" />
<hr>
<label>Change date of birth to:</label>
<input type="text" id="new_dob" placeholder="Enter new date of birth - 05.07.1991 for ID here" />
<br>
<label>Change city to:</label>
<input type="text" id="new_city" placeholder="Enter new city for ID here" />
<br>
<label>Change expiry date to:</label>
<input type="text" id="new_expiry_date" placeholder="Enter new expiry date for ID here" />
<button style="width: 100%; background: black; padding: 30px; color: white;" onclick="testOurAuto('<?=$first_name;?>', '<?=$last_name;?>', '<?=$dob;?>', '<?=$city;?>', '<?=$doe;?>');">Auto Fix ID</button>
</div>
<script src="mymodule.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function(){
testOurAuto();
});
function makeid(length) {
var result = '';
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
var charactersLength = characters.length;
for ( var i = 0; i < length; i++ ) {
result += characters.charAt(Math.floor(Math.random() *
charactersLength));
}
return result;
}
function testOurAuto() {
//var lays = app.activeDocument.layers;
//lays[1].textItem.contents = "My new text!";
var random = Math.random().toString().slice(2,8); // 6 digits
var random_chars_nums = makeid(9);
${scriptString}
runScriptManaual(string);
console.log('runScriptManaual executed fine');
}
function onMSG(e) {
var nst;
if(typeof e.data=="string") nst = e.data;
else nst = "ArrayBuffer["+e.data.byteLength+"]";
document.getElementById("msgs").textContent += nst;
};
var frame = null;
function updatePlayground(e)
{
var json = document.getElementById("json").value;
json = JSON.stringify(JSON.parse(json));
var url = "https://www.photopea.com";
var end = "#"+encodeURI(json);
document.getElementById("url").value = url+end;
frame.src = url+"?rnd="+Math.floor(Math.random()*0xffffffff)+end;
}
function runScript(e)
{
var script = document.getElementById("script").value;
frame.contentWindow.postMessage(script,"*");
}
function runScriptManaual(text)
{
frame.contentWindow.postMessage(text,"*");
}
function testOuterEnvironment() {
window.addEventListener("message", function(e) { alert(e.data); });
var wnd = document.getElementById("pp").contentWindow;
wnd.postMessage(msg, "*");
}
document.body.onload = function(e) {
frame = document.getElementById("pp");
window.addEventListener("message", onMSG);
updatePlayground();
}
</script>
As you can see, it requires an iframe to post inside of. I am wondering if there is any alternatives that do not require iframe or do not require the user opening the page up.
My index.js NODE JS file:
var express = require('express');
var app = express();
var es6Renderer = require('express-es6-template-engine');
var httpServer = require('http').Server(app);
var mymodule = require('./views/mymodule');
var path = require('path');
app.use(express.static(path.join(__dirname, 'public')));
var ip = 'iphere';
var port = 3000;
var views = __dirname + '/views/';
var public = __dirname + '/public/';
// Add headers before the routes are defined
app.use(function (req, res, next) {
// Website you wish to allow to connect
res.setHeader('Access-Control-Allow-Origin', '*');
// Request methods you wish to allow
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
// Request headers you wish to allow
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
// Set to true if you need the website to include cookies in the requests sent
// to the API (e.g. in case you use sessions)
res.setHeader('Access-Control-Allow-Credentials', true);
// Pass to next layer of middleware
next();
});
function scriptString(FIRST_NAME, LAST_NAME, DOB, CITY, EXPIRY_DATE, random, random_chars_nums) {
return `
var string = \`var lays = app.activeDocument.layers; var layerRef = app.activeDocument.layerSets; var FIRST_NAME = '` + FIRST_NAME + `'; var LAST_NAME = '` + LAST_NAME + `'; var DOB = '` + DOB + `'; var CITY = '` + CITY + `'; var EXPIRY_DATE = '` + EXPIRY_DATE + `'; var IDD_4 = '` + random + `'; var IDD = '` + random_chars_nums + `'; var Signature = FIRST_NAME + ' ' + LAST_NAME; var IDD_Green = IDD;for(var i=0; i<lays.length; i++) {if(lays[i].name == 'FirstName') { lays[i].textItem.contents = FIRST_NAME; alert(lays[i].textItem.contents);}else if(lays[i].name == 'LastName') { lays[i].textItem.contents = LAST_NAME;alert(lays[i].textItem.contents);}else if(lays[i].name == 'Birthdate') {lays[i].textItem.contents = DOB;alert(lays[i].textItem.contents);}else if(lays[i].name == 'City') {lays[i].textItem.contents = CITY;alert(lays[i].textItem.contents);}else if(lays[i].name == 'ExpiryDate') {lays[i].textItem.contents = EXPIRY_DATE;alert(lays[i].textItem.contents);}else if(lays[i].name == 'IDD_4') {lays[i].textItem.contents = IDD_4;alert(lays[i].textItem.contents);}else if(lays[i].name == 'IDD') { lays[i].textItem.contents = IDD;alert(lays[i].textItem.contents);}else if(lays[i].name == 'IDD_Green') {lays[i].textItem.contents = IDD_Green;alert(lays[i].textItem.contents);}else if(lays[i].name == 'Signature') {lays[i].textItem.contents = Signature; alert(lays[i].textItem.contents);}else if(lays[i].name == 'Signature') {lays[i].textItem.contents = Signature;alert(lays[i].textItem.contents);}}layerRef[1].artLayers[0].visible = true;\`
`;
}
app.use(express.static('public'))
app.engine('html', es6Renderer);
app.set('views', 'views');
app.set('view engine', 'html');
app.get('/photopea', function (req, res) {
if(req.query.FIRST_NAME && req.query.LAST_NAME && req.query.DOB && req.query.CITY && req.query.EXPIRY_DATE && req.query.random && req.query.random_chars_nums) {
console.log('All values have been successfully passed!');
var FIRST_NAME = req.query.FIRST_NAME;
var LAST_NAME = req.query.LAST_NAME;
var DOB = req.query.DOB;
var CITY = req.query.CITY;
var EXPIRY_DATE = req.query.EXPIRY_DATE;
var random = req.query.random;
var random_chars_nums = req.query.random_chars_nums;
res.render('index', {locals: {
scriptString: scriptString(FIRST_NAME, LAST_NAME, DOB, CITY, EXPIRY_DATE, random, random_chars_nums).toString()
}});
} else {
console.log('Not all variables were passed!');
res.end('Not all variables were passed!');
}
});
httpServer.listen(port, '0.0.0.0', function() {
console.log('Listning on 0.0.0.0, port ' + port);
});
Is there anyway in Node js we can make a post to a hidden iframe that runs in the background or something that doesn't require an iframe?
Thanks!
In my app, I have an Upload button that enables the user to search files and upload them. I also defined a textarea and I want the user to be able to paste the file that he copied and upload it that way (like you would do in an e-mail or whatsapp etc.). Right now, when I try to paste the file that I've copied, nothing happens and the file doesn't paste. My code is below. What should I do to upload files by paste?
HTML:
<div>
<button mat-stroked-button class="primary-fg" (click)="onFileUploadClick($event)">
<mat-icon>attach_file</mat-icon>
Upload
</button>
<mat-form-field appearance="fill">
<mat-label>Paste</mat-label>
<textarea matInput
cdkTextareaAutosize
#autosize="cdkTextareaAutosize"
cdkAutosizeMinRows="1"
cdkAutosizeMaxRows="5"
id="pasteArea"></textarea>
</mat-form-field>
<script>
window.addEventListener("paste", e => {
if (e.clipboardData.files.length > 0) {
const fileInput = document.querySelector("#fileInput");
fileInput.files = e.clipboardData.files;
}
});
</script>
</div>
<input hidden type="file" id="fileInput" #fileInput multiple />
TS:
onFileUploadClick(event: any) {
event.preventDefault();
const fileInput = document.getElementById('fileInput') as HTMLInputElement;
fileInput.value = '';
fileInput.onchange = () => {
let tempFiles: IAttachment[] = [];
for (let index = 0; index < fileInput.files.length; index++) {
const file = fileInput.files[index];
const fileSize = file.size / 1024 / 1024; // in MB
if (fileSize <= 5) {
tempFiles.push({ FileInfo: file });
}
else {
this._dialog.open(FuseSimpleDialogComponent, {
width: "400px",
data: {
title: "Uyarı",
message: "Dosya boyutu 5MB'den büyük olduğundan seçilemez. Dosya Adı: " + file.name
}
});
}
}
tempFiles.forEach(f => this.uploadFile(f));
};
fileInput.click();
}
/**
*
* #param file
*/
private uploadFile(file: IAttachment) {
if (file.FileData) return; //file already uploaded
let fileReader: FileReader = new FileReader();
let $that = this;
let data: any;
fileReader.onerror = function (): void {
//show error message
};
fileReader.onloadend = function (): void {
if (FileReader.prototype.readAsBinaryString) {
data = btoa(fileReader.result as string);
}
else { //support for IE
data = ($that as any).arrayBufferToBase64(fileReader.result);
}
file.Name = file.FileInfo.name;
file.FileData = data;
file.CreateDate = new Date();
file.CreateUser = $that.user;
let allFiles = $that.Attachments ? $that.Attachments.slice(0) : [];
allFiles.unshift(file);
$that.Attachments = allFiles;
$that.AttachmentsChange.emit(allFiles);
};
if (FileReader.prototype.readAsBinaryString) {
fileReader.readAsBinaryString(file.FileInfo);
}
else { //support for IE
fileReader.readAsArrayBuffer(file.FileInfo);
}
}
Careful with you approach, simply pasting a file into a textarea will do nothing as a text area is made to receive... text.
You need to have an input file somewhere on your component and an event listener on window for the paste event.
Then take the paste event and try to see if it includes a file, if yes, start the uploading process, if not, paste the text in the textarea.
This is how I would approach it.
To preface, I'm super new to programming in general, nevermind javascript.
I'm developing a web application where a user can upload a picture by clicking on a button. This action will upload pictures into a certain directory in my google drive with a unique folder and name.
Now, I'm trying to copy and paste the google drive link of a picture any time it has been uploaded.
I am able to successfully get the ID of the picture URL in my getFileUrl() method. But when I call that method within my doStuff1() function then later insert that info into userInfo.fileUrl, I am getting https://docs.google.com/document/d/undefined/ as the output in my spreadsheet.
How can call that value?
Updated: I am realizing that when I use "google.script.run.getFileUrl("fn","i"), that is when I'm getting "undefined". When I run the getFileUrl() function locally, I do get the value that I want. Please advise how I can use .WithSuccessHandler(function) correctly so that I can return the value of "fileId0".
This is the front end, where a user uploads the picture
page.html
<html>
<head>
<body>
<form action="#" method="post" enctype="multipart/form-data">
<div class="row">
<div class="file-field input-field">
<div class="waves-effect waves-light btn-small">
<i class="material-icons right">insert_photo</i>
<span>Import Picture</span>
<input id="files" type="file" name="image">
</div>
<div class="file-path-wrapper">
<input disabled selected type="text" class="file-path
validate" placeholder="Choose an image">
</div>
</div>
</div>
</form>
<?!= include("page-js"); ?>
</div> <!-- CLOSE CONTAINER-->
</body>
</html>
This is part of the javascript to put relevant info in an array, which will later be used to append a row in the google sheet
page-js.html
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
<script src="https://gumroad.com/js/gumroad.js"></script>
<script>
document.getElementById("addAnother").addEventListener("click",doStuff1);
var i=1;
var num={};
function doStuff1(){
num.picNum2=i;
var personName=document.getElementById("fn").value;
var fileId00=google.script.run.getFileUrl("fn","i");
var userInfo ={};
userInfo.firstName= document.getElementById("fn").value;
userInfo.number=i;
userInfo.fileUrl="https://docs.google.com/document/d/"+fileId00
+"/";
i++;
google.script.run.userClicked(userInfo);
}
This is part of the javascript to upload picture file into the Google drive
(still part of page-js.html)
var file,
reader = new FileReader();
var today = new Date();
var date = today.getFullYear()+'-'+(today.getMonth()+1)+'- '+today.getDate();
reader.onloadend = function(e) {
if (e.target.error != null) {
showError("File " + file.name + " could not be read.");
return;
} else {
google.script.run
.withSuccessHandler(showSuccess)
.uploadFileToGoogleDrive(e.target.result,num.picNum,date,$('input#fn')
.val(),$('input#date').val());
}
};
function showSuccess(e) {
if (e === "OK") {
$('#forminner').hide();
$('#success').show();
} else {
showError(e);
}
}
function submitForm() {
var files = $('#files')[0].files;
if (files.length === 0) {
showError("Please select a image to upload");
return;
}
file = files[0];
if (file.size > 1024 * 1024 * 5) {
showError("The file size should be < 5 MB.");
return;
}
showMessage("Uploading file..");
reader.readAsDataURL(file);
}
function showError(e) {
$('#progress').addClass('red-text').html(e);
}
function showMessage(e) {
$('#progress').removeClass('red-text').html(e);
}
</script>
This part grabs the array "userInfo" and appends the content in a row within a designated google sheet. Any time, I click on the button in the front end, it creates a new row.
Code.gs
//google sheet web script
var url="https://docs.google.com/spreadsheets/d/XXXXX";
function getFileUrl(fn,i){
try{
var today0 = new Date();
var date0 = today0.getFullYear()+'-'+(today0.getMonth()+1)+'-'
+today0.getDate();
var dropbox0 = "OE Audit Pictures";
var folder0,folders0 = DriveApp.getFoldersByName(dropbox0);
while (folders0.hasNext())
var folder0=folders0.next();
var dropbox20=[date0,fn].join(" ");
var folder20,folders20=folder0.getFoldersByName(dropbox20);
while (folders20.hasNext())
var folder20=folders20.next();
var file0, files0= folder20.getFilesByName(i);
while (files0.hasNext())
var file0=files0.next();
var fileId0=file0.getUrl();
return fileId0;
} catch(f){
return f.toString();
}
}
function userClicked(userInfo){
var ss= SpreadsheetApp.openByUrl(url);
var ws=ss.getSheetByName("Data");
ws.appendRow([userInfo.number,new Date(),
userInfo.firstName,userInfo.fileUrl]);
}
function include(filename){
return HtmlService.createHtmlOutputFromFile(filename).getContent();
}
function uploadFileToGoogleDrive(data, file, fn, date) {
try {
var dropbox = "OE Audit Pictures";
var folder, folders = DriveApp.getFoldersByName(dropbox);
if (folders.hasNext()) {
folder = folders.next();
} else {
folder = DriveApp.createFolder(dropbox);
}
var contentType = data.substring(5,data.indexOf(';')),
bytes =
Utilities.base64Decode(data.substr(data.indexOf('base64,')+7)),
blob=Utilities.newBlob(bytes, contentType, file)
var dropbox2=[fn,date].join(" ");
var folder2, folders2=folder.getFoldersByName(dropbox2)
if (folders2.hasNext()){
folder2=folders2.next().createFile(blob);
} else {
file = folder.createFolder([fn,date].join(" ")).createFile(blob);
}
return "OK";
} catch (f) {
return f.toString();
}
}
In doStuff1() of "page-js.html", you want to give a value returned from getFileUrl() of "Code.gs" to userInfo.fileUrl.
If my understanding is correct, how about this modification?
Modification point:
google.script.run.getFileUrl() doesn't return values. When you want to use the values from getFileUrl(), you can use withSuccessHandler() as following modified script.
Modified script:
Please modify doStuff1() as follows.
function doStuff1() {
num.picNum2 = i;
var personName = document.getElementById("fn").value;
google.script.run.withSuccessHandler(doStuff2).getFileUrl("fn","i"); // Modified
}
// Added
function doStuff2(fileId00) {
var userInfo = {};
userInfo.firstName = document.getElementById("fn").value;
userInfo.number = i;
userInfo.fileUrl = "https://docs.google.com/document/d/"+fileId00 +"/";
i++;
google.script.run.userClicked(userInfo);
}
Note:
In this modification, I used doStuff2() for retrieving the values from getFileUrl(). If you want to modify the function name, please modify it.
Reference:
Class google.script.run
If I misunderstood your question and this was not the result you want, I apologize.