From 86634cc4af93aedabff6b40d44aa7a9f0fb4f275 Mon Sep 17 00:00:00 2001 From: Giulio Cesare Solaroli Date: Wed, 30 Jul 2014 09:28:05 +0200 Subject: [PATCH] Implemented card archiving, both the DataModel login and UI (still very rough, though) --- .../delta/js/Clipperz/PM/DataModel/Record.js | 19 ++- .../delta/js/Clipperz/PM/DataModel/User.js | 25 +++- .../Clipperz/PM/UI/Components/Cards/List.js | 3 +- .../Clipperz/PM/UI/Components/Selections.js | 22 +++- .../Clipperz/PM/UI/Components/TagIndexItem.js | 6 +- .../delta/js/Clipperz/PM/UI/MainController.js | 112 +++++++++++++++--- frontend/delta/scss/core/layout.scss | 41 +++++++ frontend/delta/scss/style/card.scss | 12 +- frontend/delta/scss/style/selectionPanel.scss | 5 +- 9 files changed, 213 insertions(+), 32 deletions(-) diff --git a/frontend/delta/js/Clipperz/PM/DataModel/Record.js b/frontend/delta/js/Clipperz/PM/DataModel/Record.js index 9e16625..4aa0d04 100644 --- a/frontend/delta/js/Clipperz/PM/DataModel/Record.js +++ b/frontend/delta/js/Clipperz/PM/DataModel/Record.js @@ -236,6 +236,17 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.Record, Clipperz.PM.DataModel.Encrypt ], {trace:false}); }, + 'archive': function () { + return this.addTag(Clipperz.PM.DataModel.Record.archivedTag); + }, + + 'isArchived': function () { + return Clipperz.Async.callbacks("Record.isArchived", [ + MochiKit.Base.method(this, 'tags'), + function (someTags) { return MochiKit.Iter.some(someTags, MochiKit.Base.partial(MochiKit.Base.objEqual, Clipperz.PM.DataModel.Record.archivedTag))}, + ], {trace:false}); + }, + //========================================================================= 'headerNotes': function () { @@ -988,12 +999,18 @@ Clipperz.PM.DataModel.Record.defaultCardInfo = { '_reference': MochiKit.Base.methodcaller('reference'), '_searchableContent': MochiKit.Base.methodcaller('searchableContent'), '_accessDate': MochiKit.Base.methodcaller('accessDate'), + '_isArchived': MochiKit.Base.methodcaller('isArchived'), 'label': MochiKit.Base.methodcaller('label'), 'favicon': MochiKit.Base.methodcaller('favicon') }; Clipperz.PM.DataModel.Record.defaultSearchField = '_searchableContent'; -Clipperz.PM.DataModel.Record.tagChar = '#'; +Clipperz.PM.DataModel.Record.tagChar = '\uE009'; +Clipperz.PM.DataModel.Record.specialTagsWrapper = '___'; +Clipperz.PM.DataModel.Record.specialTagsConstructor = function (aTag) { + return Clipperz.PM.DataModel.Record.specialTagsWrapper + aTag + Clipperz.PM.DataModel.Record.specialTagsWrapper; +} +Clipperz.PM.DataModel.Record.archivedTag = Clipperz.PM.DataModel.Record.specialTagsConstructor('archived'); Clipperz.PM.DataModel.Record.regExpForTag = function (aTag) { return new RegExp('\\' + Clipperz.PM.DataModel.Record.tagChar + aTag, 'g'); }; diff --git a/frontend/delta/js/Clipperz/PM/DataModel/User.js b/frontend/delta/js/Clipperz/PM/DataModel/User.js index 44db261..a032678 100644 --- a/frontend/delta/js/Clipperz/PM/DataModel/User.js +++ b/frontend/delta/js/Clipperz/PM/DataModel/User.js @@ -515,8 +515,15 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, { MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.methodcaller('tags')), Clipperz.Async.collectAll, MochiKit.Base.flattenArray, - Clipperz.Base.arrayWithUniqueValues - + MochiKit.Iter.groupby, + function (someGroups) { + return MochiKit.Iter.reduce(function(aCollector, aGroup) { + if (aGroup[0] != Clipperz.PM.DataModel.Record.archivedTag) { + aCollector[aGroup[0]] = MochiKit.Iter.list(aGroup[1]).length; + } + return aCollector; + }, someGroups, {}); + } ], {trace:false}); }, @@ -534,7 +541,19 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, { return Clipperz.Async.callbacks("User.getRecordsInfo", [ MochiKit.Base.method(this, 'getRecords'), MochiKit.Base.partial(MochiKit.Base.map, Clipperz.Async.collectResults("collectResults", someInfo, {trace:false})), - Clipperz.Async.collectAll + Clipperz.Async.collectAll, + function (aResult) { + var result; + + if (shouldIncludeArchivedCards == false) { + result = MochiKit.Base.filter(function (aRecordInfo) { return !aRecordInfo['_isArchived']; }, aResult); + } else { + result = aResult; + } + + return result; + } + ], {trace:false}); }, /* diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/Cards/List.js b/frontend/delta/js/Clipperz/PM/UI/Components/Cards/List.js index c66f828..b030fba 100644 --- a/frontend/delta/js/Clipperz/PM/UI/Components/Cards/List.js +++ b/frontend/delta/js/Clipperz/PM/UI/Components/Cards/List.js @@ -39,7 +39,8 @@ Clipperz.PM.UI.Components.Cards.List = React.createClass({ renderItem: function (anItem) { var classes = { - 'selected': this.props['selectedCard'] ? this.props['selectedCard']['_reference'] == anItem['_reference'] : false + 'selected': this.props['selectedCard'] ? this.props['selectedCard']['_reference'] == anItem['_reference'] : false, + 'archived': anItem['_isArchived'] }; return React.DOM.li({'className':React.addons.classSet(classes), 'onClick': this.handleClick, 'key':anItem['_reference'], 'data-reference':anItem['_reference'], 'data-label':anItem['label']}, [ diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/Selections.js b/frontend/delta/js/Clipperz/PM/UI/Components/Selections.js index fc63a17..44e424f 100644 --- a/frontend/delta/js/Clipperz/PM/UI/Components/Selections.js +++ b/frontend/delta/js/Clipperz/PM/UI/Components/Selections.js @@ -36,8 +36,22 @@ Clipperz.PM.UI.Components.Selections = React.createClass({ MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'selectRecentCards'); }, + handleCheckboxChanges: function (anEvent) { + if (anEvent.target.checked) { + MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'showArchivedCards'); + } else { + MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'hideArchivedCards'); + } + }, + render: function () { -//console.log("Selections", this.props); + var tagInfo; + var tags; + + tagInfo = this.props['tags'] ? this.props['tags'] : {}; +// tagInfo = {"tag1":1, "tag2":2, "tag3":1, "tag4":2, "tag5":1, "tag6":2, "tag7":1, "tag8":3, "tag9":1, "tag10":11, "tag11":1, "tag12":8, "tag13":1, "tag14":3, "tag15":1, "tag16":1}; + tags = MochiKit.Base.keys(tagInfo).sort(Clipperz.Base.caseInsensitiveCompare); + return React.DOM.div({'key':'selections', 'id':'selections'}, [ React.DOM.ul({'className':'defaultSet'}, [ React.DOM.li({'className':'allCards', onClick: this.selectAll}, "All"), @@ -49,7 +63,11 @@ Clipperz.PM.UI.Components.Selections = React.createClass({ React.DOM.input({'type':'text', 'id':'searchValue', 'name':'search'}) ]) ]), - React.DOM.ul({'className':'tagList'}, MochiKit.Base.map(function (aTag) { return Clipperz.PM.UI.Components.TagIndexItem({'label':aTag}); }, this.props['tags'] ? this.props['tags'] : [])) + React.DOM.ul({'className':'tagList'}, MochiKit.Base.map(function (aTag) {return Clipperz.PM.UI.Components.TagIndexItem({'label':aTag, 'count':tagInfo[aTag]}); }, tags)), + React.DOM.div({'className':'showArchivedCards'}, [ + React.DOM.input({'type':'checkbox', 'onChange':this.handleCheckboxChanges}), + React.DOM.h5({}, "Show archived cards") + ]), ]); } diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/TagIndexItem.js b/frontend/delta/js/Clipperz/PM/UI/Components/TagIndexItem.js index 5eab63f..15dd479 100644 --- a/frontend/delta/js/Clipperz/PM/UI/Components/TagIndexItem.js +++ b/frontend/delta/js/Clipperz/PM/UI/Components/TagIndexItem.js @@ -30,6 +30,7 @@ Clipperz.PM.UI.Components.TagIndexItem = React.createClass({ propTypes: { 'label': React.PropTypes.string.isRequired, + 'count': React.PropTypes.number.isRequired, }, handleClick: function (anEvent) { @@ -38,7 +39,10 @@ Clipperz.PM.UI.Components.TagIndexItem = React.createClass({ }, render: function () { - return React.DOM.li({onClick: this.handleClick, 'data-tag':this.props['label']}, this.props['label']); + return React.DOM.li({'onClick': this.handleClick, 'data-tag':this.props['label']}, [ + React.DOM.span({'className':'tagLabel'}, this.props['label']), + React.DOM.span({'className':'tagCount'}, this.props['count']) + ]); }, //========================================================================= diff --git a/frontend/delta/js/Clipperz/PM/UI/MainController.js b/frontend/delta/js/Clipperz/PM/UI/MainController.js index f16753c..e914c7c 100644 --- a/frontend/delta/js/Clipperz/PM/UI/MainController.js +++ b/frontend/delta/js/Clipperz/PM/UI/MainController.js @@ -32,6 +32,8 @@ Clipperz.PM.UI.MainController = function() { this._user = null; this._filter = {'type':'ALL'}; + this._shouldIncludeArchivedCards = false; + this._isSelectionPanelOpen = false; this._isSettingsPanelOpen = false; @@ -76,6 +78,9 @@ Clipperz.PM.UI.MainController = function() { 'archiveCard', 'editCard', + 'showArchivedCards', + 'hideArchivedCards', + 'goBackToMainPage', 'maskClick', ]); @@ -366,6 +371,15 @@ console.log("SET USER", aUser); return this._filter; }, + + shouldIncludeArchivedCards: function () { + return this._shouldIncludeArchivedCards; + }, + + setShouldIncludeArchivedCards: function (aValue) { + this._shouldIncludeArchivedCards = aValue; + }, + //---------------------------------------------------------------------------- collectFieldInfo: function (aField) { @@ -418,6 +432,8 @@ console.log("SET USER", aUser); deferredResult.setValue('notes'); deferredResult.addMethod(aRecord, 'tags'); deferredResult.setValue('tags'); + deferredResult.addMethod(aRecord, 'isArchived'); + deferredResult.setValue('isArchived'); deferredResult.addMethod(aRecord, 'fields'); deferredResult.addCallback(MochiKit.Base.values); @@ -533,6 +549,27 @@ console.log("SET USER", aUser); return deferredResult; }, + refreshSelectedCards: function () { + return this.updateSelectedCards(this.shouldIncludeArchivedCards(), this.filter()); + }, + + refreshUI: function (selectedCardReference) { + var deferredResult; + + deferredResult = new Clipperz.Async.Deferred('MainController.refreshUI', {trace:false}); + deferredResult.addMethod(this, 'refreshSelectedCards'); + deferredResult.addMethod(this, 'renderTags'); + + if (selectedCardReference != null) { + deferredResult.addMethod(this.user(), 'getRecord', selectedCardReference); + deferredResult.addMethod(this, 'collectRecordInfo'); + deferredResult.addMethod(this, 'setPageProperties', 'mainPage', 'selectedCard'); + } + deferredResult.callback(); + + return deferredResult; + }, + //---------------------------------------------------------------------------- setPageProperties: function (aPageName, aKey, aValue) { @@ -543,19 +580,21 @@ console.log("SET USER", aUser); return aValue; }, + renderTags: function () { + return Clipperz.Async.callbacks("MainController.renderTags", [ + MochiKit.Base.method(this.user(), 'getTags'), +// MochiKit.Base.methodcaller('sort', Clipperz.Base.caseInsensitiveCompare), + MochiKit.Base.method(this, 'setPageProperties', 'mainPage', 'tags'), + ], {trace:false}); + }, + renderAccountData: function () { - var deferredResult; - - deferredResult = new Clipperz.Async.Deferred('MainController.renderAccountData', {trace:false}); - deferredResult.addMethod(this, 'setFilter', 'ALL'); - deferredResult.addMethod(this, 'updateSelectedCards', false); - - deferredResult.addMethod(this.user(), 'getTags'); - deferredResult.addMethodcaller('sort', Clipperz.Base.caseInsensitiveCompare); - deferredResult.addMethod(this, 'setPageProperties', 'mainPage', 'tags'); - deferredResult.callback(); - - return deferredResult; + return Clipperz.Async.callbacks("MainController.renderAccountData", [ + MochiKit.Base.method(this, 'setFilter', 'ALL'), +// MochiKit.Base.method(this, 'refreshSelectedCards'), +// MochiKit.Base.method(this, 'renderTags'), + MochiKit.Base.method(this, 'refreshUI', null) + ], {trace:false}); }, //========================================================================= @@ -858,8 +897,20 @@ console.log("SET USER", aUser); this.refreshCurrentPage(); }, - cardSelected_handler: function (aReference) { - this.updateSelectedCard(aReference); + cardSelected_handler: function (someInfo) { + this.updateSelectedCard(someInfo); + }, + + //---------------------------------------------------------------------------- + + askConfirmation: function (aMessage) { + var deferredResult; + + deferredResult = new Clipperz.Async.Deferred('MainController.askConfirmation', {trace:false}); + deferredResult.callback(); +// deferredResult.cancel(); + + return deferredResult; }, //---------------------------------------------------------------------------- @@ -869,11 +920,22 @@ console.log("ADD CARD CLICK"); }, deleteCard_handler: function (anEvent) { -console.log("DELETE CARD", anEvent['reference']); + return Clipperz.Async.callbacks("MainController.deleteCard_handler", [ + MochiKit.Base.method(this, 'askConfirmation', {'message':"Delete card?"}), + MochiKit.Base.method(this.user(), 'getRecord', anEvent['reference']), + MochiKit.Base.method(this.user(), 'deleteRecord'), + MochiKit.Base.method(this.user(), 'saveChanges'), + MochiKit.Base.method(this, 'refreshUI') + ], {trace:false}); }, archiveCard_handler: function (anEvent) { -console.log("ARCHIVE CARD", anEvent['reference']); + return Clipperz.Async.callbacks("MainController.archiveCard_handler", [ + MochiKit.Base.method(this.user(), 'getRecord', anEvent['reference']), + MochiKit.Base.methodcaller('archive'), + MochiKit.Base.method(this.user(), 'saveChanges'), + MochiKit.Base.method(this, 'refreshUI', anEvent['reference']) + ], {trace:true}); }, editCard_handler: function (anEvent) { @@ -889,17 +951,29 @@ console.log("EDIT CARD", anEvent['reference']); selectAllCards_handler: function () { this.setFilter('ALL'); - this.updateSelectedCards(false, this.filter()); + return this.refreshSelectedCards(); }, selectRecentCards_handler: function () { this.setFilter('RECENT'); - this.updateSelectedCards(false, this.filter()); + return this.refreshSelectedCards(); }, tagSelected_handler: function (aTag) { this.setFilter('TAG', aTag); - this.updateSelectedCards(false, this.filter()); + return this.refreshSelectedCards(); + }, + + //............................................................................ + + showArchivedCards_handler: function () { + this.setShouldIncludeArchivedCards(true); + return this.refreshSelectedCards(); + }, + + hideArchivedCards_handler: function () { + this.setShouldIncludeArchivedCards(false); + return this.refreshSelectedCards(); }, //---------------------------------------------------------------------------- diff --git a/frontend/delta/scss/core/layout.scss b/frontend/delta/scss/core/layout.scss index 0f0947d..a9569d1 100644 --- a/frontend/delta/scss/core/layout.scss +++ b/frontend/delta/scss/core/layout.scss @@ -16,6 +16,47 @@ } } +#selections { + @include flexbox(); + @include flex-direction(column); + min-height: 100%; + + ul.defaultSet { + @include flex(none); + } + + .search { + @include flex(none); + } + + ul.tagList { + @include flex(auto); + margin-left: 0px; +// overflow-y: scroll; + + li { +// @include flexbox(); + + span.tagLabel { +// @include flex(auto); + } + + span.tagCount { +// @include flex(none); + background-color: gray; + font-size: 10pt; + margin-left: 10px; + padding: 0px 4px; + @include border-radius(4px); + } + } + } + + .showArchivedCards { + @include flex(none); + } +} + #mainPanel { // background-color: $yellow; diff --git a/frontend/delta/scss/style/card.scss b/frontend/delta/scss/style/card.scss index 4897cb6..b6425b7 100644 --- a/frontend/delta/scss/style/card.scss +++ b/frontend/delta/scss/style/card.scss @@ -14,10 +14,6 @@ div.cardList { } ul { - li.selected { - background-color: yellow; - } - li { cursor: pointer; @@ -26,6 +22,14 @@ div.cardList { @include flexbox(); @include flex-direction(row); + &.selected { + background-color: yellow; + } + + &.archived { + background-color: pink; + } + .favicon { width: $cardListHeight; @include flex(none); diff --git a/frontend/delta/scss/style/selectionPanel.scss b/frontend/delta/scss/style/selectionPanel.scss index 1fe2be0..2833351 100644 --- a/frontend/delta/scss/style/selectionPanel.scss +++ b/frontend/delta/scss/style/selectionPanel.scss @@ -3,7 +3,7 @@ color: $main-alternate-text-color; font-size: 18pt; overflow: scroll; - height: 100%; +// height: 100%; $iconColumnWidth: 40px; @@ -66,4 +66,7 @@ font-size: 14pt; } } + + .showArchivedCards { + } } \ No newline at end of file