diff --git a/app/assets/javascripts/app/controllers/notes.js b/app/assets/javascripts/app/controllers/notes.js index 6f58ceca9..3d68b7370 100644 --- a/app/assets/javascripts/app/controllers/notes.js +++ b/app/assets/javascripts/app/controllers/notes.js @@ -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() { diff --git a/app/assets/javascripts/app/services/singletonManager.js b/app/assets/javascripts/app/services/singletonManager.js index 4d7969eb7..067e007d1 100644 --- a/app/assets/javascripts/app/services/singletonManager.js +++ b/app/assets/javascripts/app/services/singletonManager.js @@ -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); } } diff --git a/app/assets/javascripts/app/services/syncManager.js b/app/assets/javascripts/app/services/syncManager.js index 02596cf7f..61b6949b4 100644 --- a/app/assets/javascripts/app/services/syncManager.js +++ b/app/assets/javascripts/app/services/syncManager.js @@ -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) { diff --git a/package-lock.json b/package-lock.json index 393f5d766..29e7cc94d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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": { diff --git a/package.json b/package.json index d6bae9b0a..416939ab6 100644 --- a/package.json +++ b/package.json @@ -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" } }