From 595ce942950c9e08ce61501dfb2dfdcc23e71627 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Wed, 30 Jan 2019 17:00:16 -0600 Subject: [PATCH] Optimize digest cycle by reloading state only upon explicit data change --- .../javascripts/app/controllers/notes.js | 91 ++++++++++++++++--- .../javascripts/app/controllers/tags.js | 28 ++++-- app/assets/javascripts/app/filters/sortBy.js | 48 ---------- .../javascripts/app/filters/startFrom.js | 5 - .../javascripts/app/services/modelManager.js | 3 +- app/assets/templates/notes.html.haml | 2 +- app/assets/templates/tags.html.haml | 4 +- 7 files changed, 103 insertions(+), 78 deletions(-) delete mode 100644 app/assets/javascripts/app/filters/sortBy.js delete mode 100644 app/assets/javascripts/app/filters/startFrom.js diff --git a/app/assets/javascripts/app/controllers/notes.js b/app/assets/javascripts/app/controllers/notes.js index ff78f21ff..5264c9fef 100644 --- a/app/assets/javascripts/app/controllers/notes.js +++ b/app/assets/javascripts/app/controllers/notes.js @@ -35,15 +35,19 @@ angular.module('app') syncManager.addEventHandler((syncEvent, data) => { if(syncEvent == "local-data-loaded") { this.localDataLoaded = true; - if(this.tag && this.tag.notes.length == 0) { + if(this.tag && this.notes.length == 0) { this.createNewNote(); } } }); - modelManager.addItemSyncObserver("note-list", "Note", (allItems, validItems, deletedItems, source, sourceKey) => { + modelManager.addItemSyncObserver("note-list", "*", (allItems, validItems, deletedItems, source, sourceKey) => { + // reload our notes + this.setNotes(this.tag.notes); + // Note has changed values, reset its flags - for(var note of allItems) { + let notes = allItems.filter((item) => item.content_type == "Note"); + for(let note of notes) { note.flags = null; } @@ -53,6 +57,14 @@ angular.module('app') } }); + this.setNotes = function(notes) { + this.notes = this.sortNotes(notes, this.sortBy, this.sortReverse); + } + + this.reorderNotes = function() { + this.setNotes(this.notes); + } + this.loadPreferences = function() { let prevSortValue = this.sortBy; @@ -147,7 +159,7 @@ angular.module('app') this.panelTitle = function() { if(this.isFiltering()) { - return `${this.tag.notes.filter((i) => {return i.visible;}).length} search results`; + return `${this.notes.filter((i) => {return i.visible;}).length} search results`; } else if(this.tag) { return `${this.tag.title}`; } @@ -245,16 +257,21 @@ angular.module('app') this.noteFilter.text = ""; - if(tag.notes.length > 0) { - tag.notes.forEach((note) => { note.visible = true; }) - this.selectFirstNote(); - } else if(this.localDataLoaded) { - this.createNewNote(); - } + this.setNotes(tag.notes); + + // perform in timeout since visibleNotes relies on renderedNotes which relies on render to complete + $timeout(() => { + if(this.notes.length > 0) { + this.notes.forEach((note) => { note.visible = true; }) + this.selectFirstNote(); + } else if(this.localDataLoaded) { + this.createNewNote(); + } + }) } this.visibleNotes = function() { - return this.sortedNotes.filter(function(note){ + return this.renderedNotes.filter(function(note){ return note.visible; }); } @@ -298,9 +315,9 @@ angular.module('app') } this.createNewNote = function() { - // The "Note X" counter is based off this.tag.notes.length, but sometimes, what you see in the list is only a subset. + // The "Note X" counter is based off this.notes.length, but sometimes, what you see in the list is only a subset. // We can use this.visibleNotes().length, but that only accounts for non-paginated results, so first 15 or so. - var title = "Note" + (this.tag.notes ? (" " + (this.tag.notes.length + 1)) : ""); + var title = "Note" + (this.notes ? (" " + (this.notes.length + 1)) : ""); let newNote = modelManager.createItem({content_type: "Note", content: {text: "", title: title}}); newNote.dummy = true; this.newNote = newNote; @@ -389,12 +406,14 @@ angular.module('app') this.toggleReverseSort = function() { this.selectedMenuItem(); this.sortReverse = !this.sortReverse; + this.reorderNotes(); authManager.setUserPrefValue("sortReverse", this.sortReverse); authManager.syncUserPreferences(); } this.setSortBy = function(type) { this.sortBy = type; + this.reorderNotes(); authManager.setUserPrefValue("sortBy", this.sortBy); authManager.syncUserPreferences(); } @@ -416,4 +435,50 @@ angular.module('app') return note.tags && note.tags.length > 1; } + this.sortNotes = function(items, sortBy, reverse) { + let sortValueFn = (a, b, pinCheck = false) => { + if(!pinCheck) { + if(a.pinned && b.pinned) { + return sortValueFn(a, b, true); + } + if(a.pinned) { return -1; } + if(b.pinned) { return 1; } + } + + var aValue = a[sortBy] || ""; + var bValue = b[sortBy] || ""; + + let vector = 1; + + if(reverse) { + vector *= -1; + } + + if(sortBy == "title") { + aValue = aValue.toLowerCase(); + bValue = bValue.toLowerCase(); + + if(aValue.length == 0 && bValue.length == 0) { + return 0; + } else if(aValue.length == 0 && bValue.length != 0) { + return 1 * vector; + } else if(aValue.length != 0 && bValue.length == 0) { + return -1 * vector; + } else { + vector *= -1; + } + } + + if(aValue > bValue) { return -1 * vector;} + else if(aValue < bValue) { return 1 * vector;} + return 0; + } + + items = items || []; + var result = items.sort(function(a, b){ + return sortValueFn(a, b); + }) + return result; + }; + }); diff --git a/app/assets/javascripts/app/controllers/tags.js b/app/assets/javascripts/app/controllers/tags.js index c3d08ae94..535995413 100644 --- a/app/assets/javascripts/app/controllers/tags.js +++ b/app/assets/javascripts/app/controllers/tags.js @@ -33,6 +33,26 @@ angular.module('app') } }); + modelManager.addItemSyncObserver("note-list", "*", (allItems, validItems, deletedItems, source, sourceKey) => { + // recompute note counts + let tags = []; + if(this.tags) { + tags = tags.concat(this.tags); + } + if(this.smartTags) { + tags = tags.concat(this.smartTags); + } + + for(let tag of tags) { + var validNotes = SNNote.filterDummyNotes(tag.notes).filter(function(note){ + return !note.archived && !note.content.trashed; + }); + + tag.cachedNoteCount = validNotes.length; + } + }); + + this.panelController = {}; $rootScope.$on("user-preferences-changed", () => { @@ -146,12 +166,4 @@ angular.module('app') this.removeTag()(tag); this.selectTag(this.smartTags[0]); } - - this.noteCount = function(tag) { - var validNotes = SNNote.filterDummyNotes(tag.notes).filter(function(note){ - return !note.archived && !note.content.trashed; - }); - return validNotes.length; - } - }); diff --git a/app/assets/javascripts/app/filters/sortBy.js b/app/assets/javascripts/app/filters/sortBy.js deleted file mode 100644 index c1fa496a8..000000000 --- a/app/assets/javascripts/app/filters/sortBy.js +++ /dev/null @@ -1,48 +0,0 @@ -angular.module('app') - .filter('sortBy', function ($filter) { - return function(items, sortBy, reverse) { - let sortValueFn = (a, b, pinCheck = false) => { - if(!pinCheck) { - if(a.pinned && b.pinned) { - return sortValueFn(a, b, true); - } - if(a.pinned) { return -1; } - if(b.pinned) { return 1; } - } - - var aValue = a[sortBy] || ""; - var bValue = b[sortBy] || ""; - - let vector = 1; - - if(reverse) { - vector *= -1; - } - - if(sortBy == "title") { - aValue = aValue.toLowerCase(); - bValue = bValue.toLowerCase(); - - if(aValue.length == 0 && bValue.length == 0) { - return 0; - } else if(aValue.length == 0 && bValue.length != 0) { - return 1 * vector; - } else if(aValue.length != 0 && bValue.length == 0) { - return -1 * vector; - } else { - vector *= -1; - } - } - - if(aValue > bValue) { return -1 * vector;} - else if(aValue < bValue) { return 1 * vector;} - return 0; - } - - items = items || []; - var result = items.sort(function(a, b){ - return sortValueFn(a, b); - }) - return result; - }; - }); diff --git a/app/assets/javascripts/app/filters/startFrom.js b/app/assets/javascripts/app/filters/startFrom.js deleted file mode 100644 index 7b64038f6..000000000 --- a/app/assets/javascripts/app/filters/startFrom.js +++ /dev/null @@ -1,5 +0,0 @@ -angular.module('app').filter('startFrom', function() { - return function(input, start) { - return input.slice(start); - }; -}); diff --git a/app/assets/javascripts/app/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js index c64a139b1..d00687ee3 100644 --- a/app/assets/javascripts/app/services/modelManager.js +++ b/app/assets/javascripts/app/services/modelManager.js @@ -123,7 +123,8 @@ class ModelManager extends SFModelManager { let notTrashedPredicate = new SFPredicate("content.trashed", "=", false); predicates.push(notTrashedPredicate); } - return this.itemsMatchingPredicates(predicates); + let results = this.itemsMatchingPredicates(predicates); + return results; } trashSmartTag() { diff --git a/app/assets/templates/notes.html.haml b/app/assets/templates/notes.html.haml index c0217cc92..f3f0d8d4b 100644 --- a/app/assets/templates/notes.html.haml +++ b/app/assets/templates/notes.html.haml @@ -45,7 +45,7 @@ .scrollable .infinite-scroll#notes-scrollable{"infinite-scroll" => "ctrl.paginate()", "can-load" => "true", "threshold" => "200"} - .note{"ng-repeat" => "note in (ctrl.sortedNotes = (ctrl.tag.notes | filter: ctrl.filterNotes | sortBy: ctrl.sortBy:ctrl.sortReverse | limitTo:ctrl.notesToDisplay)) track by note.uuid", + .note{"ng-repeat" => "note in (ctrl.renderedNotes = (ctrl.notes | filter: ctrl.filterNotes | limitTo:ctrl.notesToDisplay)) track by note.uuid", "ng-click" => "ctrl.selectNote(note, true)", "ng-class" => "{'selected' : ctrl.selectedNote == note}"} %strong.red.medium-text{"ng-if" => "note.conflict_of"} Conflicted copy %strong.red.medium-text{"ng-if" => "note.errorDecrypting"} Unable to Decrypt diff --git a/app/assets/templates/tags.html.haml b/app/assets/templates/tags.html.haml index fd2e90c08..54afbcb03 100644 --- a/app/assets/templates/tags.html.haml +++ b/app/assets/templates/tags.html.haml @@ -17,7 +17,7 @@ "ng-class" => "{'selected' : ctrl.selectedTag == tag, 'faded' : !tag.content.isAllTag}"} .info %input.title{"ng-disabled" => "true", "ng-model" => "tag.title"} - .count{"ng-show" => "tag.content.isAllTag"} {{ctrl.noteCount(tag)}} + .count{"ng-show" => "tag.content.isAllTag"} {{tag.cachedNoteCount}} .tags-title-section.section-title-bar .section-title-bar-header @@ -28,7 +28,7 @@ %input.title{"ng-attr-id" => "tag-{{tag.uuid}}", "ng-click" => "ctrl.selectTag(tag)", "ng-model" => "tag.title", "ng-keyup" => "$event.keyCode == 13 && $event.target.blur()", "sn-autofocus" => "true", "should-focus" => "ctrl.newTag || ctrl.editingTag == tag", "ng-change" => "ctrl.tagTitleDidChange(tag)", "ng-blur" => "ctrl.saveTag($event, tag)", "spellcheck" => "false"} - .count {{ctrl.noteCount(tag)}} + .count {{tag.cachedNoteCount}} .red.small-text.bold{"ng-if" => "tag.conflict_of"} Conflicted copy .red.small-text.bold{"ng-if" => "tag.errorDecrypting"} Unable to Decrypt