My need: I'd like to add an "upload from clipboard" functionality into a Vaadin 23 application so that the user can paste a screenshot into an Upload field.
Known pieces of the puzzle: I know that there is a paste event (see here https://stackoverflow.com/a/51586232/10318272 or here https://developer.mozilla.org/en-US/docs/Web/API/Element/paste_event ) and there's the Vaadin Upload component ( https://vaadin.com/docs/latest/components/upload ).
Question: How can I transfer the pasted data into the Upload field?
Why initially intended solution does not work: It seems that uploading a screenshot via an Upload field is not feasible because the FileList (= model of a file input field) does not allow to add/append a new File object.
(Working) Workaround: So my workaround is a TextArea with a paste-EventListener that does a remote procedure call of a #ClientCallable method at the server.
Left component is the TextArea, right component is a preview Image.
Code:
import com.vaadin.flow.component.ClientCallable;
import com.vaadin.flow.component.html.Image;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.textfield.TextArea;
import com.vaadin.flow.server.StreamResource;
import java.io.ByteArrayInputStream;
import java.util.Base64;
public class PasteScreenshotField extends HorizontalLayout {
private final Image previewImg;
public PasteScreenshotField() {
// This could be any focusable type of component, I guess.
TextArea textField = new TextArea();
textField.setWidth("50px");
textField.setHeight("50px");
this.add(textField);
String pasteFunction = "for(const item of event.clipboardData.items) {"
+ " if(item.type.startsWith(\"image/\")) {"
+ " var blob = item.getAsFile();"
+ " var reader = new FileReader();"
+ " reader.onload = function(onloadevent) {$1.$server.upload(onloadevent.target.result);};"
+ " reader.readAsDataURL(blob);"
+ " }"
+ "}";
this.getElement().executeJs("$0.addEventListener(\"paste\", event => {"+pasteFunction+"})", textField.getElement(), this);
// Optional: Preview of the uploaded screenshot
previewImg = new Image();
// TODO: Fixed size of 50px x 50px stretches the image. Could be better.
previewImg.setWidth("50px");
previewImg.setHeight("50px");
this.add(previewImg);
}
#ClientCallable()
private void upload(String dataUrl) {
System.out.println("DataUrl: "+dataUrl);
if (dataUrl.startsWith("data:")) {
byte[] imgBytes = Base64.getDecoder().decode(dataUrl.substring(dataUrl.indexOf(',') + 1));
// Showing a preview is just one of the possible scenarios.
// TODO: check filename extension. Maybe it's not a png.
previewImg.setSrc(new StreamResource("preview.png", () -> new ByteArrayInputStream(imgBytes)));
}
}
}
Extendability: Instead of previewImg.setSrc you could do whatever you want with the uploaded file. The preview is just the proof that the screenshot goes to the server (and could go back to the client again).
Possible connection to Upload component:
If you've got an Upload component and want to extend it with this paste functionality, you can register the paste listener at the Upload component (or at some other component) and instead of previewImg.setSrc you just call this (whereas the onSucceededRunner is a BiConsumer<String, String> in my case that runs the onSucceeded stuff (updating thumbnails, setting attributes at the bound bean, ...)):
String filename = "screenshot.png";
String mimeType = "image/png";
OutputStream outputStream = uploadField.getReceiver().receiveUpload(filename, mimeType);
outputStream.write(imgBytes);
outputStream.flush();
outputStream.close();
onSucceededRunner.accept(filename, mimeTypeString);
Final result:
This is what my custom upload field looks like in the end (assembled from the code above plus an Upload field plus a TextField for the file name and a thumbnail preview). The user now has to click somewhere at that field (=focus it, because there could be more than one in a form), press Ctrl+V and then a screenshot gets uploaded (if there is any) from clipboard to Vaadin application at the server.
Related
How can I make possible that the app will load all of the images from the specific folder and then put in array and choose one image randomly? When chose one then pass to the fronted to show the image. How to do that too?
I am C# developer but not long time ago I found ElectronJS and this framework does everything easier so therefore I am moving to this framework.
I did in C# programming this way:
// basic settings.
var ext = new List<string> { ".jpg", ".gif", ".png" };
// we use same directory where program is.
string targetDirectory = Directory.GetCurrentDirectory() + "\\assets\\" + "images\\" + "animals\\";
// Here we create our list of files
// New list
// Use GetFiles to getfilenames
// Filter unwanted stuff away (like our program)
if (Directory.Exists(targetDirectory))
{
Files = new List<string>
(Directory.GetFiles(targetDirectory, "*.*", SearchOption.TopDirectoryOnly)
.Where(s => ext.Any(es => s.EndsWith(es))));
// Show first picture so we dont need wait 3 secs.
ChangePicture();
}
else
{
panel5.BackgroundImage = new Bitmap(Resources.doggy);
}
I don't know how to do in ElectronJS.
Thank you in advance the answers.
Alright. I found the solution.
However I don't understand the people who are giving negative reputation for the opened question. If they are giving negative reputation then they could explain why.
Well anyway, I did fix this issue with this way:
I created images.js file and added this:
var fs = require('fs');
function getRandImage() {
var files = fs.readdirSync('./assets/images/animals/')
/* now files is an Array of the name of the files in the folder and you can pick a random name inside of that array */
let chosenFile = files[Math.floor(Math.random() * files.length)]
console.log('../assets/images/animals/' + chosenFile);
return '../assets/images/animals/' + chosenFile;
}
module.exports = { getRandImage }
I used console to see if the value is correct, otherwise others can delete that part.
Sending the data to the renderer process:
const { getRandImage } = require('./images');
child.webContents.send('random-image', getRandImage());
I did put in the preload.js file the following (I used the starter pack electronjs github to start with something):
var { ipcRenderer } = require('electron');
ipcRenderer.on('random-image', function (event, store) {
document.getElementById("randompic").src = store;
console.log(store);
});
Same here, I did use console.log just for test the value is correct and I used to change the randompic ID related image src html to the randomly chosen image.
Hopefully I did helping those people who are newbie as me.
We are using the ms graph api to post messages to a teams channel from a internal desktop application. The main purpose is to attach images to the message. We upload the image files into the one-drive folder of the channel as shown below.
var uploadProps = new DriveItemUploadableProperties
{
ODataType = null,
AdditionalData = new Dictionary<string, object>
{
{ "#microsoft.graph.conflictBehavior", "replace" }
}
};
var session = await graphClient.Drives[driveId]
.Items[parentId].ItemWithPath(fileName).CreateUploadSession(uploadProps).Request().PostAsync(token);
int maxSliceSize = 320 * 1024;
var fileUploadTask =
new LargeFileUploadTask<DriveItem>(session, fileStream, maxSliceSize);
// Create a callback that is invoked after each slice is uploaded
IProgress<long> progress = new Progress<long>(reportAsync);
// Upload the file
var uploadResult = await fileUploadTask.UploadAsync(progress);
if (uploadResult.UploadSucceeded)
{
return uploadResult.ItemResponse;
}
We then send a message to the channel and attach the images uploaded previously as reference attachments.
var chatMsg = new ChatMessage();
chatMsg.Body = new ItemBody();
chatMsg.Body.ContentType = BodyType.Html;
chatMsg.Body.Content = msg + " " + string.Join(" ", attachments.Select(d => $"<attachment id=\"{parseEtag(d.ETag)}\"></attachment>"));
chatMsg.Attachments = attachments.Select(d => new ChatMessageAttachment()
{
Id = parseEtag(d.ETag),
ContentType = "reference",
ContentUrl = d.WebUrl,
Name = d.Name
});
return await this.graphClient.Teams[teamId].Channels[channelId].Messages
.Request()
.AddAsync(chatMsg, token);
The problem is that the message only shows the names of the attachments with no preview as seen in the message at the bottom. We want to have a preview as seen (top message) when attaching a file within the teams application.
We've tried to set the thumbnailurl property of the attachment to the thumbnail url fetched from the ms-graph api with no success.
We've uploaded a file using the teams application (with preview) and then created an identical message with the same file (same driveitem id) in our application (show's no preview). Then we fetched both messages using the graph api and could not discern any differences between the two besides the message id's ofc.
We've scoured these forums, the ms documentations and even suggestion pages and found nothing.
We have been able to show previews separately in the body of the message referencing the thumbnail urls and in messagecards but ideally we want the preview directly in the attachments.
EDIT
The thumbnail urls seem to expire after 24 hours and are therefor not a great solution.
We managed to solve exactly this problem using the Simple Upload Api, with the added ?$expand=thumbnails query parameter. I haven't tried but the query param ought to work for the endpoint you're using as well.
Pick a size from the ThumbnailSet in the upload response and add it to the body of your message as an image tag. See below:
// channel, file, extractIdFromEtag, message omitted for brevity.
// PUT /groups/{group-id}/drive/items/{parent-id}:/{filename}:/content
const uploadUrl = `https://graph.microsoft.com/beta/groups/${channel.teamId}/drive/items/root:/${channel.displayName}/${file.name}:/content?$expand=thumbnails`;
const res = await this.http.put(uploadUrl, file).toPromise(); // FYI Using Angular http service
const attachment = {
id: extractIdFromEtag(res.eTag),
contentType: 'reference',
contentUrl: res.webUrl,
name: res.name,
thumbnailUrl: res.webUrl
};
const postBody = {
subject: null,
body: {
contentType: 'html',
content: message
},
};
// This is what makes the image show in the message as if posted from teams
postBody.body.content += `<br><br><img src="${res.thumbnails[0].large.url}" alt="${res.name}"/>`;
const messageUrl = `https://graph.microsoft.com/beta/teams/${channel.teamId}/channels/${channel.id}/messages`;
const result = await this.http.post(messageUrl, postBody).toPromise();
// Done
You can also keep adding the attachment as you already do, if you want the original image attached as a file, as well as showing the image preview in the message.
I'm trying desperately to create a Firefox add-on that posts a file with the field name "Filedata" to a particular PHP script which will only work if it sees a JPG in the $_FILE["Filedata"] variable.
I put a web form with a file browser into panel.html, then I take the image and turn it into a canvas which I turn into a blob and send to main.js. I would be happy to send the file directly from panel.js, but nothing at all happens (no error message either) when I attempt to so.
In main.js, I have this code but I get an error message that FormData doesn't exist in main.js. What to do?
function ajupload(mydata) {
var fd = new FormData();
fd.append("Filedata", mydata);
const {XMLHttpRequest} = require("sdk/net/xhr");
var myrequest = new XMLHttpRequest();
myrequest.open('POST', 'MYSITE/image.php?action=upload');myrequest.setRequestHeader("Content-type","application/x-www-form-urlencoded");
myrequest.upload.addEventListener("progress", function(e) {
var percentage = Math.round((e.loaded * 100) / e.total);
}, false);
myrequest.onreadystatechange=function()
{
if (myrequest.readyState==4 && myrequest.status==200)
{
console.log("Response" + myrequest.responseText);
}
}
myrequest.send(fd);
}
I am Using Vaadin Framework. I need to Upload Files in the format of PDF,JAR & ZIP only. I tried with this code.This code is also I got from STACK OVER FLOW.
public void uploadStarted(StartedEvent event) {
// TODO Auto-generated method stub
System.out.println("***Upload: uploadStarted()");
ArrayList<String> allowedMimeTypes = new ArrayList<String>();
allowedMimeTypes.add("application/java-archive");
allowedMimeTypes.add("application/pdf");
allowedMimeTypes.add("application/zip");
String contentType = event.getMIMEType();
boolean allowed = false;
System.out.println(":::::::::::::contentType::::::"
+ contentType);
for (int i = 0; i < allowedMimeTypes.size(); i++) {
if (contentType.equalsIgnoreCase(allowedMimeTypes.get(i))) {
allowed = true;
break;
}
}
try {
if (allowed) {
System.out.println("boolean value:::::::allowed"
+ allowed);
finalDeedUpload.setReceiver(finalDeedFileUploadHandler);
finalDeedUpload.addListener(finalDeedFileUploadHandler);
} else {
showWarningNotification(
"Error:Please Upload File in Given Format", "");
}
This is working for while uploading PDf files it's working, while uploading Zip OR Jar file and any other file it is showing NULLPOINTER EXCEPTION.
Please help me.
Vaadin has a special upload component which is easy to use. There is a whole chapter in Book of Vaadin related to this component.
https://vaadin.com/book/-/page/components.upload.html
In Vaadin 14 there is a method setAcceptedFileTypes at class Upload:
MemoryBuffer buffer = new MemoryBuffer();
Upload upload = new Upload(buffer);
upload.setAcceptedFileTypes(new String[]{"application/zip", "application/pdf", "application/java-archive"});
The method setAcceptedFileTypes sets the HTML attribute accept at the <input type="file"> element and therefore limits / filters what the application user can upload.
Issue #1
When i'm uploading a file to google docs i receive status code "201" created, but when i try to open the file it seems that i'm doing something wrong, because i can't open it, and when i'm trying to download and open it on my PC i see the binary data instead of text or image. Current language is APEX, but i think it's pretty understandable.
First of all i'm getting Upload URL and then putting data to this URL;
public void getUploadURL()
{
Httprequest req = new Httprequest();
req.setEndpoint('https://docs.google.com/feeds/upload/create-session/default/private/full?convert=false');
req.setMethod('POST');
req.setHeader('GData-Version', '3.0');
req.setHeader('Authorization', 'OAuth '+accessToken);
req.setHeader('Content-Length', '359');
req.setHeader('X-Upload-Content-Type', fileType);
req.setHeader('X-Upload-Content-Length', fileSize);
Dom.Document requestDoc = new Dom.Document();
String xml =
'<?xml version=\'1.0\' encoding=\'UTF-8\'?>'
+'<entry xmlns="http://www.w3.org/2005/Atom" xmlns:docs="http://schemas.google.com/docs/2007">'
+'<title>'+fileName+'</title></entry>';
requestDoc.load(xml);
req.setBodyDocument(requestDoc);
Http h = new Http();
Httpresponse res = h.send(req);
System.debug('response=\n'+res.getHeader('Location'));
uploadFIle(res.getHeader('Location'));
}
public void uploadFIle(String uploadUrl)
{
Httprequest req = new Httprequest();
req.setEndpoint(uploadUrl);
req.setMethod('PUT');
req.setHeader('GData-Version', '3.0');
req.setHeader('Authorization', 'OAuth '+accessToken);
req.setHeader('Host', 'docs.google.com');
req.setHeader('Content-Length', fileSize);
req.setHeader('Content-Type', fileType);
req.setBody(''+binaryData);
Http h = new Http();
Httpresponse res = h.send(req);
System.debug('response=\n'+res.getBody());
}
As for "binaryData" property - i receive it from the page using javascript like this:
<input type="file" id="myuploadfield" onchange="getBinary()"/>
<script>
function getBinary()
{
var file = document.getElementById('myuploadfield').files[0];
fileSizeToController.val(file.size.toString());
fileNameToController.val(file.name.toString());
fileTypeToController.val(file.type.toString());
var r = new FileReader();
r.onload = function(){ binaryToController.val(r.result); };
r.readAsBinaryString(file);
}
</script>
r.onload = function(){ binaryToController.val(r.result); }; - this is the string that sends file binary data to my controller.
Issue #2
I'm trying to move one collection(folder) to another, and using this article (protocol tab instead of .NET). The issue is that i need to move collection instead of copying it and when i add my collection to another using this article, i'm currently adding reference to my collection instead of moving the whole collection from one place to another.
Please tell me what am i doing wrong.
Thank you for consideration.
Your "binary" data is being corrupted, when you are performing '' + binaryData.
In general, I have had more success using slicing of files, here is an example for webkit:
var chunk = this.file.webkitSlice(startByte, startByte + chunkSize, file_type);
// Upload the chunk
uploadChunk(startByte, chunk, callback);