Firebase getDownloadURL() is really slow at getting image URLs - javascript

I have a page which displays multiple (12+) objects in components, each of which has an image. This image has a src attribute, which I'm setting via a call to my Firebase storage:
this.props.storage.child('Land.jpg').getDownloadURL().then(function(url) {
var img = document.getElementById('imageGoesHere'+this.state.currentId);
img.src = url;
}.bind(this))
The storage prop is generated and passed down by a parent component:
firebase.initializeApp(config);
var storage = firebase.storage();
var storageRef = storage.ref("");
The storageRef var is passed as a prop to each child component that calls the getDownloadURL() function.
This works, but the getDownloadURL function seems to be really slow. The page renders, and then each image takes at least a second to be shown/rendered. I know it's not the raw URL's issue, since I can get the raw URL and paste it into the src and it loads fine (i.e. nearly instantly); I look at the Network tab in chrome devtools and it shows at least a second between the getDownloadURL call and the response. Is this the proper way of getting images to be shown in web pages? The Firebase Storage docs don't mention any other way.
edit: Two things; firstly I only have 5 test images in the storage, so it's not like I'm syncing to a whole bunch with the root storage ref.
Second thing; chrome devtools shows me that it's "stalling" for 1-4 seconds, and the actual request only takes 100ms tops. What can cause stalling?

This can happen because you are downloading every single image you have every time this code runs. It can happen if you sync the instance to the entire image hierarchy.
You could also figure out the reason by looking at Chrome's Network tab, and see if you have tons of files downloading, like this:

you should use the firebase public url when the image is uploaded (if you doing that way). Something like this:
uploadTask.then(function(snapshot) { return snapshot.ref.getDownloadURL() })
And save it to the DB inmediatly. Just create an "images" table and create a column named "url" or "link". That url/link you will use every time you need that image.
Now if you are uploading manually , select the image (in firebase console) and in the "file location" section, go to "access token".

Related

Image src changes when i update the image but frontend displays the old image

I have found weird bug in my application. I am using VueJS3 and typescript. I am creating a media gallery, where users can manage assets. All endpoints and backend side of application works perfectly. Currently i am working on updating image files(updating image field). My code works in theory, image url updates to a new one, but even the url is newer, image that is rendered is old image(before update). When i try to view my application in incognito window, my newer image is showing. But it does not work otherwise(works if and only user clears his cache) I hope i explained myself clearly, it is a little complicated :)
When i fetch the data from api, they don't have any src, so i have to create a property when using the data. Here is the code below.
const { res, err } = await this.$api.image.getAll(params)
res.data.items.forEach(async (image: Image<Populated>) => {
image.src = this.$fileHandler(image._id)
})
Note: File handler is a special method that gives the image source by id
Apparently your assets get cached, and there are multiple ways to solve that.
Not diving too much into caching strategies and cache control tags, just do
image.src = `${this.$fileHandler(image._id)}?_=${+new Date()}`
This will generate a unique (almost) URL for your image, invalidating every previous cached one.

With offline file:/// protocol, I'm trying to store data in local storage through an iframe. How can I make this work, or is there a better way?

Background
In our company, we install our offline documentation topics (thousands of .htm files in a folder) on our users' computers. Users view our documentation offline through their browser using the file:/// protocol.
I have a banner that appears in the footer of each .htm file that asks users to decide whether we can track user analytics (via Application Insights). Once they make their choice, we don't show the banner.
My Goal and Overall Problem
My goal is to store their choice in the browser's local storage. I can do that just fine, but the problem is this:
These are offline .htm files. There is no website domain. So, the key and value for any local storage is stored only for the .htm file they are on at the time they make their choice. If they come back to a topic they made their choice on, then yes, my script can retrieve their choice. But if they navigate to another topic in our documentation system (another .htm file), the local storage key and value don't persist to those other topics, and my script doesn't know what they chose--so then the banner pops up again.
My Workaround Idea
I doubt this is the best approach, but not having a lot of experience and not knowing what else to try, necessity becomes the mother of invention.
Here's what I'm trying:
Have my local storage requests go through a single .htm file called storage.htm, thereby getting around the above problem by always having a single point of contact (storage.htm) with the local storage.
storage.htm loads via a blank iframe.
The iframe is tacked onto each .htm topic.
When a topic .htm loads, the iframe also loads and any functions inside it become (hopefully) available for use by my main script.
When users click on the banner, I send the choice as query parameters through my main script to the iframe's src.
storage.htm contains a parseQuery() function, and inside that function, it parses any query params and then does the actual localStorage.getValue(key) and localStorage.setValue(key,value) requests.
I then want to somehow force the iframe to refresh with those parameters and then call the parseQuery() function there from my main script.
My Code
From my main script:
Attempt 1:
I've tried the top-voted item from this thread,
How to check if iframe is loaded or it has a content?
but I get stuck inside the checkIfFrameLoaded() function, and it continues to loop through the check to see if the iframe is loaded. It never loads. I think it's because the contentWindow and/or contentDocument don't work with my offline files, so I won't bore you with that code.
Attempt 2:
This is what I'd like to do as it seems cleaner:
function doIframeStorage(type,key,value){
// We get a handle on the iframe's id of 'storage'.
let storage = document.querySelector('#storage');
const src = storage.getAttribute('src');
let query = src;
if (type==='get'){
query = src + `?type=${type}&key=${key}`;
} else if (type==='set'){
query = src + `?type=${type}&key=${key}&value=${value}`;
}
storage.src = query;
storage.addEventListener('load', (e) => parseQuery());
}
But I'm running into a problem where my parseQuery() function (from storage.htm) is always undefined:
Uncaught ReferenceError: parseQuery is not defined
Is it possible to load and access my parseQuery() function from my main script like I'm doing? If so, how? I thought the addEventListener would ensure it was loaded and therefore the parseQuery() function would then be available.
Or Is there a better way to do what I'm attempting?

How do i use local files in react app without importing them

Im building a blog application and i just realized i can not use local file paths in react, and since the response of my REST api is a unique path for every image, i cant use it in react.
Is there a way of going around this without importing every single image?
This is what i am currently doing:
const imgSrc = this.state.post.img_path
const styles = {
background: 'url('+imgSrc+')'
}
and then i apply the styles to the element.
I do this cause soon im going to update it and make it so i will upload 3 different images for different screen sizes.
Solution:
Instead of storing my images outside of the public folder, i changed the upload location to store the images in the public/images folder. Thankfully i only had a few images so it wasnt that bad.
Then i set the url to be:
"https://localhost:3007/images/" + image name gotten from REST api response
and now it works.

Adobe Creative SDK for Web saving edited image

I am implementing the Adobe Creative SDK product onto my site for administrative use; administrators are able to access specific images (used on the frontpage slider), edit, and save.
The trouble is that Adobe's documentation on how to take advantage of the onSave() callback function is very vague. I had to go to the old site, Aviary, to find answers, but even there it's quite vague.
First, I am pulling the images off the server using a MySql DB query (there's at least 2 images in the slider so I wanted this to be database-driven rather than static). The images are stored as files with reference to them in the DB.
Second, once the images are displayed on the page (all of them are displayed on the administrative page along with text overlays, links, etc.), the administrator can click on the image and the Adobe Creative SDK is called and the editor window shows. All good.
Third, after editing, the admin can click "Save" and his edits are saved to the Adobe cloud temporarily (and the edited image replaces the original image on the page). What I need is for the image to ALSO save on my server OVERRIDING the original image (I don't want to have to do a DB update - too much extra work).
This is where the vague instructions at Adobe and Aviary are not helpful.
Here's my code...
(These is the functions from Adobe Creative SDK):
var featherEditor = new Aviary.Feather({
apiKey: 'myapikey',
theme: 'dark', // Check out our new 'light' and 'dark' themes!
tools: 'all',
appendTo: '',
onSave: function(imageID, newURL) {
var img = document.getElementById(imageID);
img.src = newURL;
},
onError: function(errorObj) {
alert(errorObj.message);
}
});
function launchEditor(id, src) {
featherEditor.launch({
image: id,
url: src
});
return false;
}
Essentially each image that is loaded includes in the <img> tag an onclick event which looks like:
<img id="editableimage<?php echo $srow->id ?>" src="assets/slider/<?php echo $srow->sld_image ?>" />
This calls the launchEditor function and shows the editor. When Save is clicked, among a few other things, the onSave() callback is fired and it is in that callback function that I can save the image locally.
BUT Adobe only offers the following examples to accomplish this and they make little sense to me:
First, it appears that this needs to be added to the onSave() function:
$.post('/save', {url: newURL, postdata: 'some reference to the original image'})
I'm assuming that the '/save' would actually be the php script I use to do the work...or maybe it's the location on the server to save the image...not sure. The 'postdata' says it needs "some reference to the original image", but I don't really know how to get that. I tried using "url" from the launchEditor() function because it appears that it was passed to the featherEditor, but that didn't work, it just returned a null value when I did an alert().
If someone could help me figure this out, I can easily take care of the server side php which does the saving. But I just don't know how to get the new image that Adobe has saved to override the old image on my server. Thanks!
The Image Editor onSave() method
onSave() is just a hook; it is the method called after the image save is complete. What you put inside of the onSave() method is entirely up to you.
Just to illustrate, you could 1) replace the original image element's source with the new edited image URL, then 2) close the editor:
onSave: function(imageID, newURL) {
originalImage.src = newURL;
featherEditor.close();
}
You could even just put a console log in there, but that wouldn't do much for the user.
Again, onSave() is just a hook that is used after the save is complete, and its content is completely up to you.
Posting to your server
The code you showed in your question is just an example of how you might post the data to your server using jQuery within the Image Editor's onSave() method. There is no requirement that you do it this way; you don't even have to use jQuery.
For clarity, here's a look at that example again:
$.post('/save', {url: newURL, postdata: 'some reference to the original image'})
The endpoint
The example above uses the jQuery post() method to hit a /save endpoint on your server. This endpoint could be anything you want it to be. If you have an endpoint on your server called /upload-image, you could use that instead.
In your case, at this endpoint would be the PHP script that handles the image file save and database update.
The post data
The second argument to post() is an object with the data that you want to pass. In this example, we're passing:
the newURL of the edited image so your server can do something with it (see Important note below)
a reference to the original image (arbitrarily named postdata here) so your server can know what image was edited
You can put anything you want in this object. It depends on what your server script needs. But at the bare minimum, I would think it would need the newURL and likely some way to reference the original image in your database.
Important note: as noted in the Creative SDK for web Image Editor guide, the newURL that you receive in the onSave() method is a temporary URL. It expires in 72 hours. That means that your server script needs to save the image itself to your server. If you only store the URL in your database, your images will start disappearing in 72 hours.

Javascript FileSaver saves empty files after writing large number of files

Background
I'm working on an internal project that basically can generate a video on the client side, but since there are no JavaScript video encoders I'm aware of, I'm just exporting each frame individually. I need to avoid uploading to the server; this is all happening on the client side.
Implementation
I'm using this FileSaver.js (more specifically, Chrome's webkit FileSystem API) to save a large number of PNGs generated by an HTML5 canvas. I set Chrome to automatically download to a specific folder, so when I hit 'Save' it just takes off and saves something like 20 images per second. This works perfectly for my purposes.
If I could use JSZip to compress all these frames into one file before offering it to the client to save, I would, but I haven't even tried because there's just no way the browser will have enough memory to generate ~8000 640x480 PNGs and then compress them.
Problem
The problem is that after a certain number of images, every file downloaded is empty. Chrome even starts telling me in the download bar that the file is 0 bytes. Repeated on the same project with the same export settings, the empty saves start at exactly the same time. For example, with one project, I can save the first 5494 frames before it chokes. (I know this is an insanely large number, but I can't help that.) I tried setting a 10ms delay between saves, but that didn't have any effect. I haven't tried a larger delay because exporting takes a very long time as it is.
I checked the blob.size and it's never zero. I suspect it's exceeding some quota, but there are no errors thrown; it just silently fails to either write to the sandbox or copy the file to the user-specified location.
Questions
How can I detect these empty saves? Prevent them? Is there a better way to do this? Am I just screwed?
EDIT: Actually, after debugging FileSaver.js, I realized that it's not even using webkitRequestFileSystem; it returns when it gets here:
if (can_use_save_link) {
object_url = get_object_url(blob);
save_link.href = object_url;
save_link.download = name;
if (click(save_link)) {
filesaver.readyState = filesaver.DONE;
dispatch_all();
return;
}
}
So, it looks like it's not even using the FileSystem API, and therefore I have no idea how to empty the storage before it's full.
EDIT 2: I tried moving the "if (can_use_save_link)" block to inside the "writer.onwriteend" function, and changing it to this:
if (can_use_save_link) {
save_link.href = file.toURL();
save_link.download = name;
click(save_link);
}else{
target_view.location.href = file.toURL();
}
The result is I'm able to save all 8260 files (about 1.5GB total) since it's now using storage with a quota. Before, the files didn't show up in the HTML5 FileSystem because I assume you didn't need to put them there if the anchor element supported the 'download' attribute.
I was also able to comment out the code that appends ".download" to the filename, and I had to provide an empty anonymous function as an argument to both instances of "file.remove()".
Use JSZip, it won't use too much memory if you disable compression (which is the default). To manually disable compression anyways, make sure to pass compression: "STORE" when calling zip.generate().
I ended up modifying FileSaver.js (see "EDIT 2" in the original post).

Categories

Resources