I am using meteor-slingshot to upload a file. I want to set the progress bar percentage when it changes.
This is how I do it now.
{{percentage}}
percentage: number;
uploadButton() {
// First start to upload
// ...
// Then track the progress
setInterval(() => {
this.percentage = uploader.progress(); // The API uploader.progress() returns a number
}, 1000);
}
Is there a smart way using RxJS or something else to track the number change without using setInterval?
Thanks
uploader.progress() is a reactive source. So I end up with using Tracker.
this.autorun(() => this.percentage = uploader.progress());
Related
I am working on react native application I use firebase as my backend. I fetch data from firebase real time database and render it on the page. But now I want my application to be supported offline.
I used following two functions for rendering.
For listings from database
const loadListings = () => {
let data = [];
listingRef.orderByChild("created_at").on("value", (snapshot) => {
data = [];
snapshot.forEach((listing) => {
data.push(listing.val());
});
cache.store("listings", data.slice(0, 10)); // only stores latest ten listings
setListings(data);
setLoading(false);
});
};
and then use it inside useEffect like.
useEffect(() => {
loadListings();
}, []);
and for listings from cache I used this.
const loadListingsCached = async () => {
let data = await cache.get("listings");
setListings(data);
};
Now I cant put a check inside firs function as effect hook will run only one time and initialy network status is null. its not defined.
how do I achieve this?
by the way link to package I used for detecting connectivity
Edit
I used this hook as second argument to useEffect() but didn't work for me
const netInfo = useNetInfo();
I
What you want to achieve is make the code different depending on what is the network status. In the answer linked by #Rohit there is my answer about how to check the network connectivity with Net Info Package.
What you have to do is make the effect dependant on the status change. You should pass it as a argument to the effect.
const netInfo = useNetInfo();
useEffect(() => {
loadListings();
}, [netInfo]);
This way the code will always run when a network change is detected. I hope this is what you wanted to achive. Please be more specific about you goal and what is the problem. Current questions does not specify if the hook is not working, or the rendering function does not trigger etc.
I have refactor problems because my code dosnt work to the new versions of angular and angularfire.
Error
The line: upload.url = uploadTask.snapshot.downloadURL; is undefined.
Code
uploadTask.on(firebase.storage.TaskEvent.STATE_CHANGED,
// three observers
// 1.) state_changed observer
(snapshot) => {
// upload in progress
upload.progress = (uploadTask.snapshot.bytesTransferred / uploadTask.snapshot.totalBytes) * 100;
console.log(upload.progress);
},
// 2.) error observer
(error) => {
// upload failed
console.log(error);
},
// 3.) success observer
(): any => {
upload.url = uploadTask.snapshot.downloadURL; //?!?!UNDEFINED
upload.name = upload.file.name;
this.saveFileData(upload);
}
);
Questions
I had tried different solutions from stackoverflow but it dosnt really work. Most of the example is also more about how to retrieve the image but i want to set the variable upload.url to a value.
Another question:
I'm new to angular and web. Will it take long time to change it to firestore? The code is based on realtime firebase.
To get the downloadURL, you have to call the getDownloadURL() method of the Storage Reference Object.
Try this:
uploadTask.snapshot.ref.getDownloadURL()
.subscribe(url => console.log(url))
I need to add current system time into child data field.
I'm using TypeScript, but this might still give you and idea how you could do it.
My code uses the event.timestamp property to get date and time:
export const onWrite = functions.database.ref('/{databaseName}/{tableName}/{key}').onCreate((event) => {
let ref = event.data.ref;
let isCreate = event.eventType == 'providers/google.firebase.database/eventTypes/ref.create';
ref.once('value').then(e => {
// Cloud functions are sometimes executed with a delay and the record might not exist anymore.
if (e.val() === null) {
return;
}
if (isCreate) {
return ref.update({
'createdDateTime': event.timestamp
});
}
});
});
The created events for clients won't include this added data yet, only a later change event does.
I'm haven't investigated yet if this can be fixed (perhaps by making use of transaction).
I saw your image description and understood u want to add system time into firebase.
If you want to do you can do that by , like below
var fb_db=firebase.database().ref('treeName');
var key=fb_db.push().key;
var updatenode={}
updatenode[key+"\"]= new Date();
fb_db.update(updatenode).then(function(){
alert("Success")
})
Let's say that two users do changes to the same document while offline, but in different sections of the document. If user 2 goes back online after user 1, will the changes made by user 1 be lost?
In my database, each row contains a JS object, and one property of this object is an array. This array is bound to a series of check-boxes on the interface. What I would like is that if two users do changes to those check-boxes, the latest change is kept for each check-box individually, based on the time the when the change was made, not the time when the syncing occurred. Is GroundDB the appropriate tool to achieve this? Is there any mean to add an event handler in which I can add some logic that would be triggered when syncing occurs, and that would take care of the merging ?
The short answer is "yes" none of the ground db versions have conflict resolution since the logic is custom depending on the behaviour of conflict resolution eg. if you want to automate or involve the user.
The old Ground DB simply relied on Meteor's conflict resolution (latest data to the server wins) I'm guessing you can see some issues with that depending on the order of when which client comes online.
Ground db II doesn't have method resume it's more or less just a way to cache data offline. It's observing on an observable source.
I guess you could create a middleware observer for GDB II - one that checks the local data before doing the update and update the client or/and call the server to update the server data. This way you would have a way to handle conflicts.
I think to remember writing some code that supported "deletedAt"/"updatedAt" for some types of conflict handling, but again a conflict handler should be custom for the most part. (opening the door for reusable conflict handlers might be useful)
Especially knowing when data is removed can be tricky if you don't "soft" delete via something like using a "deletedAt" entity.
The "rc" branch is currently grounddb-caching-2016 version "2.0.0-rc.4",
I was thinking about something like:
(mind it's not tested, written directly in SO)
// Create the grounded collection
foo = new Ground.Collection('test');
// Make it observe a source (it's aware of createdAt/updatedAt and
// removedAt entities)
foo.observeSource(bar.find());
bar.find() returns a cursor with a function observe our middleware should do the same. Let's create a createMiddleWare helper for it:
function createMiddleWare(source, middleware) {
const cursor = (typeof (source||{}).observe === 'function') ? source : source.find();
return {
observe: function(observerHandle) {
const sourceObserverHandle = cursor.observe({
added: doc => {
middleware.added.call(observerHandle, doc);
},
updated: (doc, oldDoc) => {
middleware.updated.call(observerHandle, doc, oldDoc);
},
removed: doc => {
middleware.removed.call(observerHandle, doc);
},
});
// Return stop handle
return sourceObserverHandle;
}
};
}
Usage:
foo = new Ground.Collection('test');
foo.observeSource(createMiddleware(bar.find(), {
added: function(doc) {
// just pass it through
this.added(doc);
},
updated: function(doc, oldDoc) {
const fooDoc = foo.findOne(doc._id);
// Example of a simple conflict handler:
if (fooDoc && doc.updatedAt < fooDoc.updatedAt) {
// Seems like the foo doc is newer? lets update the server...
// (we'll just use the regular bar, since thats the meteor
// collection and foo is the grounded data
bar.update(doc._id, fooDoc);
} else {
// pass through
this.updated(doc, oldDoc);
}
},
removed: function(doc) {
// again just pass through for now
this.removed(doc);
}
}));
In my client UI I have a form with differents search criterias, and I'd like to reactively update the results list. The search query is transformed into a classical minimongo selector, saved in a Session variable, and then I have observers to do things with the results:
// Think of a AirBnb-like application
// The session variable `search-query` is updated via a form
// example: Session.set('search-query', {price: {$lt: 100}});
Offers = new Meteor.Collection('offers');
Session.setDefault('search-query', {});
resultsCursor = Offers.find(Session.get('search-query'));
// I want to add and remove pins on a map
resultCursor.observe({
added: Map.addPin,
removed: Map.removePin
});
Deps.autorun(function() {
// I want to modify the cursor selector and keep the observers
// so that I would only have the diff between the old search and
// the new one
// This `modifySelector` method doesn't exist
resultsCursor.modifySelector(Session.get('search-query'));
});
How could I implement this modifySelector method on the cursor object?
Basically I think this method needs to update the compiled version of the cursor, ie the selector_f attribute, and then rerun observers (without losing the cache of the previous results). Or is there any better solution?
Edit: Some of you have misunderstood what I'm trying to do. Let me provide a complete example:
Offers = new Meteor.Collection('offers');
if (Meteor.isServer && Offers.find().count() === 0) {
for (var i = 1; i < 4; i++) {
// Inserting documents {price: 1}, {price: 2} and {price: 3}
Offers.insert({price:i})
}
}
if (Meteor.isClient) {
Session.setDefault('search-query', {price:1});
resultsCursor = Offers.find(Session.get('search-query'));
resultsCursor.observe({
added: function (doc) {
// First, this added observer is fired once with the document
// matching the default query {price: 1}
console.log('added:', doc);
}
});
setTimeout(function() {
console.log('new search query');
// Then one second later, I'd like to have my "added observer" fired
// twice with docs {price: 2} and {price: 3}.
Session.set('search-query', {});
}, 1000);
}
This doesn't solve the problem in the way you seem to be wanting to, but I think the result is still the same. If this is a solution you explicitly don't want, let me know and I can remove the answer. I just didn't want to put code in a comment.
Offers = new Meteor.Collection('offers');
Session.setDefault('search-query', {});
Template.map.pins = function() {
return Offers.find(Session.get('search-query'));
}
Template.map.placepins = function(pins) {
// use d3 or whatever to clear the map and then place all pins on the map
}
Assuming your template is something like this:
<template name="map">
{{placepins pins}}
</template>
One solution is to manually diff the old and the new cursors:
# Every time the query change, do a diff to add, move and remove pins on the screen
# Assuming that the pins order are always the same, this use a single loop of complexity
# o(n) rather than the naive loop in loop of complexity o(n^2)
Deps.autorun =>
old_pins = #pins
new_pins = []
position = 0
old_pin = undefined # This variable needs to be in the Deps.autorun scope
# This is a simple algo to implement a kind of "reactive cursor"
# Sorting is done on the server, it's important to keep the order
collection.find(Session.get('search-query'), sort: [['mark', 'desc']]).forEach (product) =>
if not old_pin?
old_pin = old_pins.shift()
while old_pin?.mark > product.mark
#removePin(old_pin)
old_pin = old_pins.shift()
if old_pin?._id == product._id
#movePin(old_pin, position++)
new_pins.push(old_pin)
old_pin = old_pins.shift()
else
newPin = #render(product, position++)
new_pins.push(newPin)
# Finish the job
if old_pin?
#removePin(old_pin)
for old_pin in old_pins
#removePin(old_pin)
#pins = new_pins
But it's a bit hacky and not so efficient. Moreover the diff logic is already implemented in minimongo so it's better to reuse it.
Perhaps an acceptable solution would be to keep track of old pins in a local collection? Something like this:
Session.setDefault('search-query', {});
var Offers = new Meteor.Collection('offers');
var OldOffers = new Meteor.Collection(null);
var addNewPin = function(offer) {
// Add a pin only if it's a new offer, and then mark it as an old offer
if (!OldOffers.findOne({_id: offer._id})) {
Map.addPin(offer);
OldOffers.insert(offer);
}
};
var removePinsExcept = function(ids) {
// Clean out the pins that no longer exist in the updated query,
// and remove them from the OldOffers collection
OldOffers.find({_id: {$nin: ids}}).forEach(function(offer) {
Map.removePin(offer);
OldOffers.remove({_id: offer._id});
});
};
Deps.autorun(function() {
var offers = Offers.find(Session.get('search-query'));
removePinsExcept(offers.map(function(offer) {
return offer._id;
}));
offers.observe({
added: addNewPin,
removed: Map.removePin
});
});
I'm not sure how much faster this is than your array answer, though I think it's much more readable. The thing you need to consider is whether diffing the results as the query changes is really much faster than removing all the pins and redrawing them each time. I would suspect that this might be a case of premature optimization. How often do you expect a user to change the search query, such that there will be a significant amount of overlap between the results of the old and new queries?
I have the same problem in my own hobby Meteor project.
There is filter session var where selector is storing. Triggering any checkbox or button changes filter and all UI rerender.
That solution have some cons and the main - you can't share app state with other users.
So i realized that better way is storing app state in URL.
May be it is also better in your case?
Clicking button now change URL and UI rendering based on it. I realize it with FlowRouter.
Helpful reading: Keeping App State on the URL