Is there any best way to resize and save files to s3 in meteor.
I though about using cfs packages, but they put too much load on server.
To directly upload images to the s3 I'm using slingshot which is very fine.
but slingshot takes only file objects as inputs it doesn't take streams to store the files.
Is there any option to resize the image in client side and pass it to the slingshot package
package: https://github.com/CulturalMe/meteor-slingshot
issue: https://github.com/CulturalMe/meteor-slingshot/issues/36
Yes it's possible with clientside-image-manipulation, here's my untested interpretation of the docs:
Template.upload.events({
'change #image-upload': function(event, target) {
var uploader = new Slingshot.Upload("myFileUploads");
var file = event.target.files[0];
var img = null;
processImage(file, 300, 300, function(data){
uploader.send(data, function (error, downloadUrl) {
if(error)
throw new Meteor.Error('upload', error);
Meteor.users.update(Meteor.userId(), {$push: {"profile.files": downloadUrl}});
});
});
}
});
There are other image related plugins worth investigating at atmosphere.js
I've found that the following works in order to integrate Clientside Image Manipulation with Slingshot (asynchronous code with ES6 promises):
var uploader;
function b64ToBlob(b64Data, contentType, sliceSize) {
var byteNumbers, i, slice;
var offset = 0;
var byteCharacters = atob(b64Data);
var byteArrays = [];
sliceSize = sliceSize || 512;
byteArrays = [];
while (offset < byteCharacters.length) {
slice = byteCharacters.slice(offset, offset + sliceSize);
byteNumbers = [];
for (i = 0; i < slice.length; ++i) {
byteNumbers.push(slice.charCodeAt(i));
}
byteArrays.push(new Uint8Array(byteNumbers));
offset += sliceSize;
}
return new Blob(byteArrays, {type: contentType});
}
uploader = new Slingshot.Upload("pictures");
new Promise(function (resolve) {
processImage(file, 300, 300, resolve);
}).then(function (dataUri) {
var match = /^data:([^;]+);base64,(.+)$/.exec(dataUri);
return [file.name, match[1], match[2]];
}).then(function (params) {
var name = params[0];
var type = params[1];
var b64 = params[2];
return new Promise(function (resolve, reject) {
var blob = b64ToBlob(b64, type);
blob.name = name;
uploader.send(blob, function (error, downloadUrl) {
if (error != null) {
reject(error.message);
} else {
resolve(downloadUrl);
}
});
});
});
Conversion from Base64 to blob is borrowed from https://stackoverflow.com/a/16245768/1238764.
There are a lot of image manipulation plugins. The one I use is: cropper
It's a jquery based plugin which basically does the thing you want + a bit more. After manipulation you can convert the image to a canvas and with the dataToUrl() method you can pass the data of the new manipulated image to any datasource of your choice.
Related
I have an ASP MVC5 API that generates an excel.xlsx and returns it in a FileContentResult. All in memory, as I can not save the file to the server disk. It works ok if I access the url directly.
I have an AngularJS application that needs to pass a huge Json to the API and receive the generated .xlsx file.
I'm trying the following:
Controller:
public async Task<FileContentResult> Excel([FromBody]GetGeneralFilterVM operationHistoryFilter = null)
{
var ListaOperazioni = await GetListaOperazioniData(operationHistoryFilter);
var Totals = await GetExcelTotalsData(operationHistoryFilter);
var excelExport = new ExcelExportEntity(new object[]
{
ListaOperazioni,
Totals,
});
var preFile = excelExport.DoExcel();
var arraybits = preFile;
var file = File(arraybits, "application/vnd.ms-excel", "OperationHistory.xlsx");
return file;
}
Angular:
$scope.exportExcel = () => {
$.ajax({
cache: false,
url: appPath + "controller/Excel",
data: filter,
success: function (response) {
var file = new Blob([response], { type: "application/vnd.ms-excel" });
var fileName = "excelFeliz.xlsx";
saveAs(file, fileName);
},
error: function (ajaxContext) {
alert('Export error: ' + ajaxContext.responseText);
}
});
}
This will even download a file, but when trying to open it is corrupted.
My insistence on AJAX is because of the GetGeneralFilterVM that I am getting in the controller, it contains sub objects with many properties would be very complicated to put this as parameters in the url.
I also have no way to generate and return a url to download, because I can not save the file to the server disk.
Any idea?
Change the mime type to "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" instead of " "application/vnd.ms-excel"
After much searching I found a way, this one works 100%. Instead of using AJAX, which seems to have problems with blob, I used an XMLHttpRequest call. Note: The controller has not been changed.
getExportExcel: function (filter) {
var json_upload = "operationHistoryFilter=" + JSON.stringify(filter);
var url = appPath + "OperationHistoryReport/ExcelGeneral";
var fileName = "excel.xlsx"
var request = new XMLHttpRequest();
request.open('POST', url, true);
request.setRequestHeader('Content-Type', 'application/json');
request.responseType = 'blob';
request.onload = function (e) {
if (this.status === 200) {
var blob = this.response;
if (window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveBlob(blob, fileName);
}
else {
var downloadLink = window.document.createElement('a');
var contentTypeHeader = request.getResponseHeader("Content-Type");
downloadLink.href = window.URL.createObjectURL(new Blob([blob], { type: contentTypeHeader }));
downloadLink.download = fileName;
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
}
}
};
request.send(JSON.stringify(filter));
}
I'm trying to do the following:
The user fill a form and send it in .JSON to the server
With the form, the server generate some .CSV files and put them all together in a .ZIP file.
The server send the .ZIP file and the user download it.
After some research I have wrote this code:
My Controller:
[HttpPost]
[Route("routeToMyAPI")]
public HttpResponseMessage Process(Form form)
{
var result = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StreamContent(<streamToMyGeneratedZipFile>)
};
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = "fileName.zip"
};
return result;
}
My Service:
angular.module('app')
.factory('MyService', function ($http) {
return {
Process: function (form) {
var req = $http.post('rest/example/process', form);
return req;
}
};
});
My Controller:
this.submit = function () {
var Form = {};
var formPost = MyService.Process(Form);
formPost.then(function (data) {
var a = document.createElement('a');
var blob = new Blob([data], { 'type': "application/octet-stream" });
a.href = URL.createObjectURL(blob);
a.download = "fileName.zip";
a.click();
}, function (error) {
alert('An error occured !');
});
};
I have parts 1 & 2 working, but I don't have find the way to call my ASP API to download the .ZIP file.
When I call the submit method of my Controller, I have a fileName.zip who is downloaded on my PC but when I try to open it Windows says to me that it's not a valid archive.
What did I do wrong ? I'm a rookie in angularjs and ASP so any help will be welcomed.
Thanks in advance.
Several issues with your code:
After ZipArchive does its work, the position of the stream will be at the end. So you must reset it to the beginning like this before sending it:
zipStream.Position = 0;
Since you're setting the content type and file name of the file on the server already, just parse it on the client side.
var headers = data.headers(); //$http resolves to data, status, headers, config
var regex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
var match = regex.exec(headers["content-disposition"]);
var fileName = match[1] || "myZipFile.zip";
fileName = fileName.replace(/\"/g, ""); //replace trailing and leading slashes
var contentType = headers["content-type"] || "application/octet-stream";
IE will not allow you to open blobs directly. You must use msSaveOrOpenBlob or msSaveBlob.
var blob = new Blob([data.data], { type: contentType });
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(blob, fileName);
} else {
var a = document.createElement('a');
var objectUrl = URL.createObjectURL(blob);
a.href = URL.createObjectURL(blob);
a.download = match[1];
a.click();
}
One last thing: the previous code won't work on firefox because firefox doesn't support clic(). Thanks to this thread this can be solved by adding this little snippet of code:
HTMLElement.prototype.click = function() {
var evt = this.ownerDocument.createEvent('MouseEvents');
evt.initMouseEvent('click', true, true, this.ownerDocument.defaultView, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
this.dispatchEvent(evt);
}
I'm working on a multi-tenant apps. Each app will have a video module which takes several youtube playlists/channels id as input.
Apps = {_id: "app01", playlists:['PL4IrNZLvgtED4RTH3xCf9085hwqKmb4lM','PLlYqpJ9JE-J8QIlua0KsBIxp-VQLyM_nO']}
Apps = {_id: "app02", playlists:['id22','id23']}
and so on.
When user redirect to address localhost:3000/app/:_id, the app subscribe to Meteor.subscribe('apps',_id).
Now on the client side, i need to render a page that shows a list of playlists (or list of channels) with playlist's name, playlist thumbnail; and then if user clicks on the name/thumbnail, it displays another page with a list of videos belongs to that playlist/channel.
What i am doing is to have a helper to retrieve all youtube playlists's info:
Template.Videos.helpers({
myPlaylist:function(){
return Apps.findOne({}, {
transform: function (doc){
playlists = doc.playlists;
doc.date = new Date(); //for testing
doc.videos = [];
for (var i=0;i<playlists.length;i++){
console.log('find playlist: ',playlists[i]);
var url = 'https://gdata.youtube.com/feeds/api/playlists/'+playlists[i]+'?v=2&alt=json';
$.getJSON(url, function(response){
var video={};
video._id = response.feed.yt$playlistId.$t;
video.title = response.feed.title;
video.entry = response.feed.entry;
videos.push(video);
})//end getJSON
}//end for
doc.videos = videos;
console.log('Result:', doc);
return doc;
}
});
}
});
The problem is in my template, I can see myPlaylist.date as a result of transform, but I cannot see myPlaylist.videos (although I see the console.log result has data)
Does anyone has an idea how to solve this? Very appreciate!
As #Ian Jones commented, $.getJSON is asynchronous so doc.videos will never have a value where you’ve put your console.log. To solve this, use the ReactiveVar package:
Template.Videos.onCreated(function() {
this.videos = new ReactiveVar([]);
var self = this;
Apps.findOne({}, {
transform: function (doc){
playlists = doc.playlists;
for (var i=0;i<playlists.length;i++){
console.log('find playlist: ',playlists[i]);
var url = 'https://gdata.youtube.com/feeds/api/playlists/'+playlists[i]+'?v=2&alt=json';
$.getJSON(url, function(response){
var video={};
video._id = response.feed.yt$playlistId.$t;
video.title = response.feed.title;
video.entry = response.feed.entry;
var videos = self.videos.get();
videos.push(video);
self.videos.set(videos);
})//end getJSON
}//end for
}
});
}
});
Template.Videos.helpers({
myPlaylist:function(){
var instance = Template.instance();
return instance.videos.get();
}
});
Thanks for all your helps. I was unwilling to do reactive-var from the beginning, but your answer motivated me to go for it, big thanks.
This is my final code:
Template.Videos.created = function(){
var instance = this;
instance.videos = new ReactiveVar([]);
instance.autorun(function(){
MenusList.findOne({}, {
transform: function (doc){
playlists = doc.playlists;
for (var i=0;i<playlists.length;i++){
console.log('find playlist: ',playlists[i]);
var url = 'https://gdata.youtube.com/feeds/api/playlists/'+playlists[i]+'?v=2&alt=json';
$.getJSON(url, function(response){
var video={};
video._id = response.feed.yt$playlistId.$t;
video.title = response.feed.title;
video.entry = response.feed.entry;
var videos = instance.videos.get();
videos.push(video);
instance.videos.set(videos);
})//end getJSON
}//end for
return doc;
}//end transform
});//end findOne
});//end autorun
}
Template.Videos.helpers({
myPlaylist:function(){
var instance = Template.instance();
return instance.videos.get();
}
});
I'm trying to get the original dimensions for an image while uploading it to a database. Actually it would be great to get all of it's original metadata (XMP, Adobe). But even getting the dimensions is not working:
Template.pixUpload.events({
'change .myPixInput': function(event, template) {
FS.Utility.eachFile(event, function(file) {
// get the image's width
var img = event.target.files[0]
var imgwidth = img.width;
console.log('width: ' + width);
var newFile = new FS.File(file);
newFile.metadata = {width: imgwidth};
MyPix.insert(newFile, function (err, fileObj) {
//If !err, we have inserted new doc with ID fileObj._id, and
//kicked off the data upload using HTTP
});
});
}
});
I use Imagemagick to get all kinds of metadata (like EXIF) from my images.
var assetStore = new FS.Store.GridFS("assetFiles", {
transformWrite: function(fileObj, readStream, writeStream) {
readStream.pipe(writeStream);
// write the image data to the fileobj
getBinaryData(readStream, FS.Utility.safeCallback(function(err, binary) {
var imageData = Imagemagick.identify({
data: binary
});
fileObj.update({
$push: {
data: imageData
}
});
}));
}
});
getBinaryData is a async function that returns the binary data of my image.
I use a package called classcraft:imagemagick since the graphicsmagick package does not give you as much metadata as imagemagick
This works! – copy/modified from a discussion with Sanjo at GitHub. Only problem is I don't fully understand what's happening. Can anyone help me out?
var OriginalsStore = new FS.Store.FileSystem("OriginalPix", {
path: pathToOriginalsFolder,
transformWrite: function (fileObj, readStream, writeStream) {
// write original image to writeStream, no transformations
readStream.pipe(writeStream);
gm(readStream, fileObj.name())
.size({bufferStream: true}, FS.Utility.safeCallback(function (err, size) {
if (err) {
// handle the error
} else {
fileObj.update({$set: {'metadata.width': size.width, 'metadata.height': size.height}});
}
}));
}
});
This is what are you looking for?
A normal image don't come with the 'width; field, it comes with type,name,dateMod,dateUp,and size.(well atleast my files)
Template.pixUpload.events({
'change .myPixInput': function(event, template) {
FS.Utility.eachFile(event, function(file) {
// get the image's width
var img = event.target.files[0]
var imgwidth = img.width;
console.log('width: ' + width);
console.log(img.lastModified);
console.log(img.lastModifiedDate);
console.log(img.name);
console.log(img.size);
console.log(img.type);
var newFile = new FS.File(file);
newFile.metadata = {
width: imgwidth
name:img.name,
size:img.size,
type:img.type,
lstModDate:img.lastModifiedDate
lstDate:img.lastModified
};
MyPix.insert(newFile, function (err, fileObj) {
//If !err, we have inserted new doc with ID fileObj._id, and
//kicked off the data upload using HTTP
});
});
}
});
Test it
I am looking for Drag And Drop File Upload component, using Knockout + .NET WebApi technologies.
I have found File Api project, it doesn't support old browsers, but I can live with it. The code is here: https://github.com/khayrov/khayrov.github.com/tree/master/jsfiddle/knockout-fileapi.
It creates custom Knockout Bindings, some code parts:
HTML:
<input type="file" accept="image/*" data-bind="file: imageFile, fileObjectURL: imageObjectURL, fileBinaryData: imageBinary"/>
Knockout JS:
ko.bindingHandlers.file = {
init: function(element, valueAccessor) {
$(element).change(function() {
var file = this.files[0];
if (ko.isObservable(valueAccessor())) {
valueAccessor()(file);
}
});
},
update: function(element, valueAccessor, allBindingsAccessor) {
var file = ko.utils.unwrapObservable(valueAccessor());
var bindings = allBindingsAccessor();
if (bindings.fileObjectURL && ko.isObservable(bindings.fileObjectURL)) {
var oldUrl = bindings.fileObjectURL();
if (oldUrl) {
windowURL.revokeObjectURL(oldUrl);
}
bindings.fileObjectURL(file && windowURL.createObjectURL(file));
}
if (bindings.fileBinaryData && ko.isObservable(bindings.fileBinaryData)) {
if (!file) {
bindings.fileBinaryData(null);
} else {
var reader = new FileReader();
reader.onload = function(e) {
bindings.fileBinaryData(e.target.result);
};
reader.readAsArrayBuffer(file);
}
}
}
Unfourtunately I do not understand if I can reuse this code and integrated it within some Drag And Drop File upload component?
Is there any existing DnD file upload component that can be used with knockout + webapi?
See this http://jsfiddle.net/3LT9d/
function noopHandler(evt) {
evt.preventDefault();
return false;
}
ko.bindingHandlers.dropUpload = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
element.addEventListener('dragenter', noopHandler, false);
element.addEventListener('dragover', noopHandler, false);
element.addEventListener('drop', function (evt) {
evt.preventDefault();
var value = valueAccessor();
for (var i = 0; i < evt.dataTransfer.files.length; i++) {
value.push(evt.dataTransfer.files[i]);
}
}, false);
}
};
How it works:
The dropUpload custom binding populates an observable array with the dragged files
To upload the files, the new API FormData is used since backward compatibility seems to be not an issue. FormData is easier to work with.
Follow this article to find out how to allow your webapi to accept FormData
http://www.asp.net/web-api/overview/working-with-http/sending-html-form-data,-part-2