Initial load content priority

This commit is contained in:
Mo Bitar
2019-01-30 14:44:20 -06:00
parent ff93d9e5e5
commit c6a90ec513
5 changed files with 19 additions and 168 deletions

View File

@@ -16,16 +16,7 @@ angular.module('app')
link:function(scope, elem, attrs, ctrl) {
scope.$watch('ctrl.tag', (tag, oldTag) => {
if(tag) {
if(tag.needsLoad) {
scope.$watch('ctrl.tag.didLoad', function(didLoad){
if(didLoad) {
tag.needsLoad = false;
ctrl.tagDidChange(tag, oldTag);
}
});
} else {
ctrl.tagDidChange(tag, oldTag);
}
ctrl.tagDidChange(tag, oldTag);
}
});
}
@@ -46,6 +37,11 @@ angular.module('app')
for(var note of allItems) {
note.flags = null;
}
// select first note if none is selected
if(!this.selectedNote) {
this.selectFirstNote();
}
});
this.loadPreferences = function() {
@@ -231,16 +227,8 @@ angular.module('app')
}
this.noteFilter.text = "";
this.setNotes(tag.notes);
}
this.setNotes = function(notes) {
notes.forEach((note) => {
note.visible = true;
})
var createNew = this.visibleNotes().length == 0;
this.selectFirstNote(createNew);
tag.notes.forEach((note) => { note.visible = true; })
}
this.visibleNotes = function() {

View File

@@ -1,149 +1,7 @@
/*
The SingletonManager allows controllers to register an item as a singleton, which means only one instance of that model
should exist, both on the server and on the client. When the SingletonManager detects multiple items matching the singleton predicate,
the oldest ones will be deleted, leaving the newest ones. (See 4/28/18 update. We now choose the earliest created one as the winner.).
class SingletonManager extends SFSingletonManager {
(This no longer fully applies, See 4/28/18 update.) We will treat the model most recently arrived from the server as the most recent one. The reason for this is,
if you're offline, a singleton can be created, as in the case of UserPreferneces. Then when you sign in, you'll retrieve your actual user preferences.
In that case, even though the offline singleton has a more recent updated_at, the server retreived value is the one we care more about.
4/28/18: I'm seeing this issue: if you have the app open in one window, then in another window sign in, and during sign in,
click Refresh (or autorefresh occurs) in the original signed in window, then you will happen to receive from the server the newly created
Extensions singleton, and it will be mistaken (it just looks like a regular retrieved item, since nothing is in saved) for a fresh, latest copy, and replace the current instance.
This has happened to me and many users.
A puzzling issue, but what if instead of resolving singletons by choosing the one most recently modified, we choose the one with the earliest create date?
This way, we don't care when it was modified, but we always, always choose the item that was created first. This way, we always deal with the same item.
*/
class SingletonManager {
constructor($rootScope, modelManager) {
this.$rootScope = $rootScope;
this.modelManager = modelManager;
this.singletonHandlers = [];
$rootScope.$on("initial-data-loaded", (event, data) => {
this.resolveSingletons(modelManager.allItems, null, true);
this.initialDataLoaded = true;
})
/*
If an item alternates its uuid on registration, singletonHandlers might need to update
their local reference to the object, since the object reference will change on uuid alternation
*/
modelManager.addModelUuidChangeObserver("singleton-manager", (oldModel, newModel) => {
for(var handler of this.singletonHandlers) {
if(handler.singleton && SFPredicate.ItemSatisfiesPredicates(newModel, handler.predicates)) {
// Reference is now invalid, calling resolveSingleton should update it
handler.singleton = null;
this.resolveSingletons([newModel]);
}
}
})
$rootScope.$on("sync:completed", (event, data) => {
// Wait for initial data load before handling any sync. If we don't want for initial data load,
// then the singleton resolver won't have the proper items to work with to determine whether to resolve or create.
if(!this.initialDataLoaded) {
return;
}
// The reason we also need to consider savedItems in consolidating singletons is in case of sync conflicts,
// a new item can be created, but is never processed through "retrievedItems" since it is only created locally then saved.
// HOWEVER, by considering savedItems, we are now ruining everything, especially during sign in. A singleton can be created
// offline, and upon sign in, will sync all items to the server, and by combining retrievedItems & savedItems, and only choosing
// the latest, you are now resolving to the most recent one, which is in the savedItems list and not retrieved items, defeating
// the whole purpose of this thing.
// Updated solution: resolveSingletons will now evaluate both of these arrays separately.
this.resolveSingletons(data.retrievedItems, data.savedItems);
})
}
registerSingleton(predicates, resolveCallback, createBlock) {
/*
predicate: a key/value pair that specifies properties that should match in order for an item to be considered a predicate
resolveCallback: called when one or more items are deleted and a new item becomes the reigning singleton
createBlock: called when a sync is complete and no items are found. The createBlock should create the item and return it.
*/
this.singletonHandlers.push({
predicates: predicates,
resolutionCallback: resolveCallback,
createBlock: createBlock
});
}
resolveSingletons(retrievedItems, savedItems, initialLoad) {
retrievedItems = retrievedItems || [];
savedItems = savedItems || [];
for(let singletonHandler of this.singletonHandlers) {
var predicates = singletonHandler.predicates;
let retrievedSingletonItems = this.modelManager.filterItemsWithPredicates(retrievedItems, predicates);
// We only want to consider saved items count to see if it's more than 0, and do nothing else with it.
// This way we know there was some action and things need to be resolved. The saved items will come up
// in filterItemsWithPredicate(this.modelManager.allItems) and be deleted anyway
let savedSingletonItemsCount = this.modelManager.filterItemsWithPredicates(savedItems, predicates).length;
if(retrievedSingletonItems.length > 0 || savedSingletonItemsCount > 0) {
/*
Check local inventory and make sure only 1 similar item exists. If more than 1, delete newest
Note that this local inventory will also contain whatever is in retrievedItems.
*/
var allExtantItemsMatchingPredicate = this.modelManager.itemsMatchingPredicates(predicates);
/*
Delete all but the earliest created
*/
if(allExtantItemsMatchingPredicate.length >= 2) {
let sorted = allExtantItemsMatchingPredicate.sort((a, b) => {
/*
If compareFunction(a, b) is less than 0, sort a to an index lower than b, i.e. a comes first.
If compareFunction(a, b) is greater than 0, sort b to an index lower than a, i.e. b comes first.
*/
return a.created_at < b.created_at ? -1 : 1;
});
// The item that will be chosen to be kept
let winningItem = sorted[0];
// Items that will be deleted
// Delete everything but the first one
let toDelete = sorted.slice(1, sorted.length);
for(var d of toDelete) {
this.modelManager.setItemToBeDeleted(d);
}
this.$rootScope.sync();
// Send remaining item to callback
singletonHandler.singleton = winningItem;
singletonHandler.resolutionCallback(winningItem);
} else if(allExtantItemsMatchingPredicate.length == 1) {
if(!singletonHandler.singleton) {
// Not yet notified interested parties of object
var singleton = allExtantItemsMatchingPredicate[0];
singletonHandler.singleton = singleton;
singletonHandler.resolutionCallback(singleton);
}
}
} else {
// Retrieved items does not include any items of interest. If we don't have a singleton registered to this handler,
// we need to create one. Only do this on actual sync completetions and not on initial data load. Because we want
// to get the latest from the server before making the decision to create a new item
if(!singletonHandler.singleton && !initialLoad && !singletonHandler.pendingCreateBlockCallback) {
singletonHandler.pendingCreateBlockCallback = true;
singletonHandler.createBlock((created) => {
singletonHandler.singleton = created;
singletonHandler.pendingCreateBlockCallback = false;
singletonHandler.resolutionCallback(created);
});
}
}
}
constructor(modelManager, syncManager) {
super(modelManager, syncManager);
}
}

View File

@@ -4,6 +4,11 @@ class SyncManager extends SFSyncManager {
super(modelManager, storageManager, httpManager, $timeout, $interval);
this.$rootScope = $rootScope;
this.$compile = $compile;
// Content types appearing first are always mapped first
this.contentTypeLoadPriority = [
"SN|UserPreferences", "SN|Privileges",
"SN|Component", "SN|Theme"];
}
presentConflictResolutionModal(items, callback) {

6
package-lock.json generated
View File

@@ -8097,9 +8097,9 @@
"dev": true
},
"standard-file-js": {
"version": "0.3.37",
"resolved": "https://registry.npmjs.org/standard-file-js/-/standard-file-js-0.3.37.tgz",
"integrity": "sha512-Je/vBxfWWHBrlDeLLW9Q4QLC+R9SxlYLhB8IpJVp6GZMHD+ET/LBPN+qlGJtUUI0Jr8HHJ1AUfEam9SNZYo1YQ==",
"version": "0.3.39",
"resolved": "https://registry.npmjs.org/standard-file-js/-/standard-file-js-0.3.39.tgz",
"integrity": "sha512-UBBxOKVHJrnmc5u/0mKwmShI5yrZyPi9z3G7wwkP6UaH4wA0p4sWaO33PgJZjaF4YsYNoa9sBidIbqdtTRn4iw==",
"dev": true
},
"static-extend": {

View File

@@ -45,7 +45,7 @@
"serve-static": "^1.13.2",
"sn-models": "0.1.12",
"sn-stylekit": "2.0.13",
"standard-file-js": "0.3.37",
"standard-file-js": "0.3.39",
"grunt-shell": "^2.1.0"
}
}