Initial load content priority
This commit is contained in:
@@ -16,16 +16,7 @@ angular.module('app')
|
|||||||
link:function(scope, elem, attrs, ctrl) {
|
link:function(scope, elem, attrs, ctrl) {
|
||||||
scope.$watch('ctrl.tag', (tag, oldTag) => {
|
scope.$watch('ctrl.tag', (tag, oldTag) => {
|
||||||
if(tag) {
|
if(tag) {
|
||||||
if(tag.needsLoad) {
|
ctrl.tagDidChange(tag, oldTag);
|
||||||
scope.$watch('ctrl.tag.didLoad', function(didLoad){
|
|
||||||
if(didLoad) {
|
|
||||||
tag.needsLoad = false;
|
|
||||||
ctrl.tagDidChange(tag, oldTag);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
ctrl.tagDidChange(tag, oldTag);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -46,6 +37,11 @@ angular.module('app')
|
|||||||
for(var note of allItems) {
|
for(var note of allItems) {
|
||||||
note.flags = null;
|
note.flags = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// select first note if none is selected
|
||||||
|
if(!this.selectedNote) {
|
||||||
|
this.selectFirstNote();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.loadPreferences = function() {
|
this.loadPreferences = function() {
|
||||||
@@ -231,16 +227,8 @@ angular.module('app')
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.noteFilter.text = "";
|
this.noteFilter.text = "";
|
||||||
this.setNotes(tag.notes);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setNotes = function(notes) {
|
tag.notes.forEach((note) => { note.visible = true; })
|
||||||
notes.forEach((note) => {
|
|
||||||
note.visible = true;
|
|
||||||
})
|
|
||||||
|
|
||||||
var createNew = this.visibleNotes().length == 0;
|
|
||||||
this.selectFirstNote(createNew);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.visibleNotes = function() {
|
this.visibleNotes = function() {
|
||||||
|
|||||||
@@ -1,149 +1,7 @@
|
|||||||
/*
|
class SingletonManager extends SFSingletonManager {
|
||||||
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.).
|
|
||||||
|
|
||||||
(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,
|
constructor(modelManager, syncManager) {
|
||||||
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.
|
super(modelManager, syncManager);
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ class SyncManager extends SFSyncManager {
|
|||||||
super(modelManager, storageManager, httpManager, $timeout, $interval);
|
super(modelManager, storageManager, httpManager, $timeout, $interval);
|
||||||
this.$rootScope = $rootScope;
|
this.$rootScope = $rootScope;
|
||||||
this.$compile = $compile;
|
this.$compile = $compile;
|
||||||
|
|
||||||
|
// Content types appearing first are always mapped first
|
||||||
|
this.contentTypeLoadPriority = [
|
||||||
|
"SN|UserPreferences", "SN|Privileges",
|
||||||
|
"SN|Component", "SN|Theme"];
|
||||||
}
|
}
|
||||||
|
|
||||||
presentConflictResolutionModal(items, callback) {
|
presentConflictResolutionModal(items, callback) {
|
||||||
|
|||||||
6
package-lock.json
generated
6
package-lock.json
generated
@@ -8097,9 +8097,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"standard-file-js": {
|
"standard-file-js": {
|
||||||
"version": "0.3.37",
|
"version": "0.3.39",
|
||||||
"resolved": "https://registry.npmjs.org/standard-file-js/-/standard-file-js-0.3.37.tgz",
|
"resolved": "https://registry.npmjs.org/standard-file-js/-/standard-file-js-0.3.39.tgz",
|
||||||
"integrity": "sha512-Je/vBxfWWHBrlDeLLW9Q4QLC+R9SxlYLhB8IpJVp6GZMHD+ET/LBPN+qlGJtUUI0Jr8HHJ1AUfEam9SNZYo1YQ==",
|
"integrity": "sha512-UBBxOKVHJrnmc5u/0mKwmShI5yrZyPi9z3G7wwkP6UaH4wA0p4sWaO33PgJZjaF4YsYNoa9sBidIbqdtTRn4iw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"static-extend": {
|
"static-extend": {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
"serve-static": "^1.13.2",
|
"serve-static": "^1.13.2",
|
||||||
"sn-models": "0.1.12",
|
"sn-models": "0.1.12",
|
||||||
"sn-stylekit": "2.0.13",
|
"sn-stylekit": "2.0.13",
|
||||||
"standard-file-js": "0.3.37",
|
"standard-file-js": "0.3.39",
|
||||||
"grunt-shell": "^2.1.0"
|
"grunt-shell": "^2.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user