Diffstat (limited to 'frontend/delta/js/Clipperz/PM/UI/Components') (more/less context) (show whitespace changes)
6 files changed, 216 insertions, 23 deletions
diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/CardDetail.js b/frontend/delta/js/Clipperz/PM/UI/Components/CardDetail.js index df514a2..12ddce3 100644 --- a/frontend/delta/js/Clipperz/PM/UI/Components/CardDetail.js +++ b/frontend/delta/js/Clipperz/PM/UI/Components/CardDetail.js @@ -32,100 +32,153 @@ Clipperz.PM.UI.Components.CardDetail = React.createClass({ propTypes: { card: React.PropTypes.object.isRequired }, getInitialState: function () { return { // showSearch: false, // searchTimer: null, + unmaskedFields: new Clipperz.Set(), starred: false }; }, handleDirectLoginClick: function (aDirectLoginReference, anEvent) { MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'runDirectLogin', {record:this.props.card['reference'], directLogin:aDirectLoginReference}); }, + toggleFieldVisibility: function (aField, anEvent) { + var unmaskedFields; + var fieldReference; + + unmaskedFields = this.state['unmaskedFields']; + fieldReference = aField['reference'] + if (unmaskedFields.contains(fieldReference)) { + unmaskedFields.remove(fieldReference) + } else { + unmaskedFields.add(fieldReference) + } + + this.setState({'unmaskedFields': unmaskedFields}); + }, + + handleGoAction: function (aField, anEvent) { + var newWindow; + + newWindow = MochiKit.DOM.currentWindow().open(aField['value'], '_blank'); + newWindow.focus(); + }, + + handleEmailAction: function (aField, anEvent) { + MochiKit.DOM.currentWindow().location = 'mailto:' + aField['value']; + }, + //========================================================================= normalizeFieldValue: function (aValue) { var result = []; var rows = aValue.split('\n'); for (var i = 0; i < rows.length; i++) { if (i > 0) { result.push(React.DOM.br()); } result.push(rows[i].replace(/[\s]/g, '\u00A0')); } return result; }, - renderField: function (aField) { -//console.log("FIELD", aField); - var actionLabel; + renderFieldActionButton: function (aField) { +// var actionLabel; + var result; if (aField['actionType'] == 'URL') { - actionLabel = "go"; + result = React.DOM.div({className:'actionWrapper', onClick:MochiKit.Base.method(this, 'handleGoAction', aField)}, [ + React.DOM.a({className:aField['actionType']}, "go") + ]); } else if (aField['actionType'] == 'PASSWORD') { - actionLabel = "locked"; + var icon; + + if (this.state['unmaskedFields'].contains(aField['reference'])) { + icon = "unlocked"; + } else { + icon = "locked"; + } + result = React.DOM.div({className:'actionWrapper', onClick:MochiKit.Base.method(this, 'toggleFieldVisibility', aField)}, [ + React.DOM.a({className:aField['actionType']}, icon) + ]); } else if (aField['actionType'] == 'EMAIL') { - actionLabel = "email"; + result = React.DOM.div({className:'actionWrapper', onClick:MochiKit.Base.method(this, 'handleEmailAction', aField)}, [ + React.DOM.a({className:aField['actionType']}, "email") + ]); } else { - actionLabel = ""; + result = null; + } + + return result; + }, + + renderField: function (aField) { +//console.log("FIELD", aField); + var fieldExtraClass; + + fieldExtraClass = aField['actionType']; + if (this.state['unmaskedFields'].contains(aField['reference'])) { + fieldExtraClass = fieldExtraClass + ' unlocked'; } - return React.DOM.div({className:'listItem ' + aField['actionType']}, [ + return React.DOM.div({className:'listItem ' + fieldExtraClass, key:aField['reference']}, [ React.DOM.div({className:'fieldWrapper'}, [ React.DOM.div({className:'fieldInnerWrapper'}, [ React.DOM.div({className:'labelWrapper'}, React.DOM.span({className:'label'}, aField['label'])), - React.DOM.div({className:'valueWrapper'}, React.DOM.span({className:'value ' + aField['actionType']}, this.normalizeFieldValue(aField['value']))) + React.DOM.div({className:'valueWrapper'}, React.DOM.span({className:'value ' + fieldExtraClass}, this.normalizeFieldValue(aField['value']))) ]) ]), - React.DOM.div({className:'actionWrapper'}, [ - React.DOM.div({className:aField['actionType']}, actionLabel) - ]) + this.renderFieldActionButton(aField) +// React.DOM.div({className:'actionWrapper'}, [ +// React.DOM.div({className:aField['actionType']}, actionLabel) +// ]) ]); }, renderDirectLogin: function (aDirectLogin) { //console.log("DIRECT LOGIN", aDirectLogin); return React.DOM.div({className:'listItem', onClick:MochiKit.Base.method(this, 'handleDirectLoginClick', aDirectLogin['reference'])}, [ React.DOM.div({className:'labelWrapper'}, React.DOM.span({className:'label'}, aDirectLogin['label'])), React.DOM.div({className:'faviconWrapper'}, React.DOM.img({className:'favicon', src:aDirectLogin['favicon']})), React.DOM.div({className:'directLoginLinkWrapper'}, React.DOM.span({className:'directLoginLink'}, "go")) ]); }, handleBackClick: function (anEvent) { - window.history.back(); +// window.history.back(); + MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'goBack'); }, handleStarClick: function (anEvent) { this.setState({starred: !this.state['starred']}); }, //========================================================================= render: function () { var card = this.props.card; - var starredStatus = (this.state['starred'] ? "starred" : "unstarred"); +// var starredStatus = (this.state['starred'] ? "starred" : "unstarred"); if ((typeof(card['fields']) != 'undefined') && (card['notes'] != '')) { card['fields'].push({ 'actionType': 'NOTES', 'isHidden': false, 'label': "notes", 'reference': "notes", 'value': card['notes'] }) } return React.DOM.div({className:'cardDetail'}, [ React.DOM.div({className:'header'}, [ React.DOM.div({className:'titleWrapper'}, React.DOM.div({className:'title'}, card.title)), -// React.DOM.div({className:'titleWrapper'}, React.DOM.div({className:'title'}, card.title + ' ' + card.title + ' ' + card.title + ' ' + card.title)), React.DOM.div({className:'backWrapper'}, React.DOM.a({className:'button back', onClick:this.handleBackClick}, "back")), - React.DOM.div({className:'starWrapper'}, React.DOM.a({className:'star', onClick:this.handleStarClick}, starredStatus)) +// React.DOM.div({className:'starWrapper'}, React.DOM.a({className:'star', onClick:this.handleStarClick}, starredStatus)) ]), React.DOM.div({className:'content'}, [ card.fields ? React.DOM.div({className:'fields'}, MochiKit.Base.map(this.renderField, card.fields)) : null, card.directLogins ? React.DOM.div({className:'directLogins'}, MochiKit.Base.map(this.renderDirectLogin, card.directLogins)): null ]), React.DOM.div({className:'footer'}, [ /* // React.DOM.a({className:'cancel'}, "cancel"), diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/CardList.js b/frontend/delta/js/Clipperz/PM/UI/Components/CardList.js index 66d20f1..5a44a4a 100644 --- a/frontend/delta/js/Clipperz/PM/UI/Components/CardList.js +++ b/frontend/delta/js/Clipperz/PM/UI/Components/CardList.js @@ -92,21 +92,28 @@ console.log("focusOnSearchField", this.refs['searchField']); result = null; } return result; }, //========================================================================= + showPreferences: function (anEvent) { + MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'showPreferences', anEvent); + }, + + //========================================================================= + cardItem: function (aRecordReference) { var reference = aRecordReference['_reference']; var selectedCard = (reference == this.props.selectedCard); - return React.DOM.div({className:'listItem', onClick:MochiKit.Base.method(this, 'handleClickOnCardDetail', reference)}, [ + // TODO: verify if it is possible to put the onClick handler on the container 'div', instead of adding it to each 'div' item. + return React.DOM.div({className:'listItem', key:reference, onClick:MochiKit.Base.method(this, 'handleClickOnCardDetail', reference)}, [ React.DOM.div({className:'labelWrapper'}, React.DOM.span({className:'label'}, aRecordReference.label)), // React.DOM.div({className:'labelWrapper'}, React.DOM.span({className:'label'}, aRecordReference.label + ' ' + aRecordReference.label + ' ' + aRecordReference.label + ' ' + aRecordReference.label + ' ' + aRecordReference.label)), React.DOM.div({className:'faviconWrapper'}, aRecordReference.favicon ? React.DOM.img({className:'favicon', src:aRecordReference.favicon}) : React.DOM.div({className:'favicon'}, '\u00A0')), React.DOM.div({className:'detailLinkWrapper'}, React.DOM.span({className:'detailLink ' + (selectedCard ? 'icon-spin' : '')}, (selectedCard ? "loading" : "detail"))) ]); }, handleClickOnCardDetail: function (aRecordReference, anEvent) { @@ -141,19 +148,19 @@ console.log("focusOnSearchField", this.refs['searchField']); //========================================================================= render: function() { return React.DOM.div(null, [ React.DOM.div({className:'header'}, [ React.DOM.a({className:'account'}, 'clipperz'), React.DOM.div({className:'features'}, [ - React.DOM.a({className:'addCard'}, 'add'), +// React.DOM.a({className:'addCard'}, 'add'), React.DOM.a({className:'search ' + (this.state.showSearch ? 'selected' : ''), onClick:this.toggleSearch}, 'search'), - React.DOM.a({className:'settings'}, 'settings') + React.DOM.a({className:'settings', onClick:this.showPreferences}, 'settings') ]), // this.searchBox() ]), this.searchBox(), React.DOM.div({className:'content cardList'}, this.cardListItems()), ]); } diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/Checkbox.js b/frontend/delta/js/Clipperz/PM/UI/Components/Checkbox.js new file mode 100644 index 0000000..9538063 --- a/dev/null +++ b/frontend/delta/js/Clipperz/PM/UI/Components/Checkbox.js @@ -0,0 +1,44 @@ +/* + +Copyright 2008-2013 Clipperz Srl + +This file is part of Clipperz, the online password manager. +For further information about its features and functionalities please +refer to http://www.clipperz.com. + +* Clipperz is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + +* Clipperz is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Affero General Public License for more details. + +* You should have received a copy of the GNU Affero General Public + License along with Clipperz. If not, see http://www.gnu.org/licenses/. + +*/ + +Clipperz.PM.UI.Components.Checkbox = React.createClass({ +// http://development.tobypitman.com/iphoneCheckboxes/iphoneCheckboxes2.html + + propTypes: { + 'checked': React.PropTypes.bool.isRequired, + 'id': React.PropTypes.string.isRequired, + 'eventHandler': React.PropTypes.func.isRequired + }, + + //========================================================================= + + render: function () { + return React.DOM.div({className:'checkbox', onClick:this.props['eventHandler']}, [ + React.DOM.input({name:this.props['id'], id:this.props['id'], value:this.props['id'], type:'checkbox', checked:this.props['checked']}), + React.DOM.label({className:'check', 'for':this.props['id']}), + React.DOM.label({className:'info', 'for':this.props['id']}, "enable local storage") + ]); + } + + //========================================================================= +}); diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/LoginForm.js b/frontend/delta/js/Clipperz/PM/UI/Components/LoginForm.js index 2b5b4a4..801549f 100644 --- a/frontend/delta/js/Clipperz/PM/UI/Components/LoginForm.js +++ b/frontend/delta/js/Clipperz/PM/UI/Components/LoginForm.js @@ -87,17 +87,17 @@ Clipperz.PM.UI.Components.LoginForm = React.createClass({ || (this.state['pin'] != '') ) && !this.props['disabled']; }, loginForm: function () { registrationLink = React.DOM.div({'className':'registrationLink'}, [ - React.DOM.a({'onClick':this.handleRegistrationLinkClick}, "Need an account") + React.DOM.a({'onClick':this.handleRegistrationLinkClick}, "Sign up") ]); return React.DOM.div({'className':'loginForm credentials'},[ React.DOM.form({onChange: this.handleChange, onSubmit:this.handleCredentialSubmit}, [ React.DOM.div(null,[ React.DOM.label({'for':'name'}, "username"), React.DOM.input({'type':'text', 'name':'name', 'ref':'username', 'placeholder':"username", 'key':'username', 'autoCapitalize':'none'}), React.DOM.label({'for':'passphrase'}, "passphrase"), React.DOM.input({'type':'password', 'name':'passphrase', 'ref':'passphrase', 'placeholder':"passphrase", 'key':'passphrase'}) diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/Overlay.js b/frontend/delta/js/Clipperz/PM/UI/Components/Overlay.js index cc4a06c..cb5f81a 100644 --- a/frontend/delta/js/Clipperz/PM/UI/Components/Overlay.js +++ b/frontend/delta/js/Clipperz/PM/UI/Components/Overlay.js @@ -89,19 +89,20 @@ Clipperz.Base.extend(Clipperz.PM.UI.Components.Overlay, Object, { this.hideSpinner(); MochiKit.Base.bind(aFunctionToShowResult, this)(); this.setMessage(aMessage); MochiKit.Async.callLater(delay, MochiKit.Base.bind(this.hide, this)) }, 'hide': function () { - MochiKit.DOM.removeElementClass(this.element(), 'ios-overlay-show'); - MochiKit.DOM.addElementClass(this.element(), 'ios-overlay-hide'); - MochiKit.Async.callLater(1, MochiKit.Style.hideElement, this.element()); + var element = this.element(); + MochiKit.DOM.removeElementClass(element, 'ios-overlay-show'); + MochiKit.DOM.addElementClass(element, 'ios-overlay-hide'); + MochiKit.Async.callLater(1, MochiKit.Style.hideElement, element); }, 'hideSpinner': function () { MochiKit.Style.hideElement(this.getElement('spinner')); }, 'showDoneIcon': function () { MochiKit.Style.showElement(this.getElement('done')); diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/PreferencePage.js b/frontend/delta/js/Clipperz/PM/UI/Components/PreferencePage.js new file mode 100644 index 0000000..822acc2 --- a/dev/null +++ b/frontend/delta/js/Clipperz/PM/UI/Components/PreferencePage.js @@ -0,0 +1,88 @@ +/* + +Copyright 2008-2013 Clipperz Srl + +This file is part of Clipperz, the online password manager. +For further information about its features and functionalities please +refer to http://www.clipperz.com. + +* Clipperz is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + +* Clipperz is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Affero General Public License for more details. + +* You should have received a copy of the GNU Affero General Public + License along with Clipperz. If not, see http://www.gnu.org/licenses/. + +*/ + +Clipperz.PM.UI.Components.PreferencePage = React.createClass({ + + getDefaultProps: function () { + return { + } + }, + + propTypes: { +// card: React.PropTypes.object.isRequired +// checked: React.PropTypes.boolean.isRequired + }, + + getInitialState: function () { +// return { +// shouldStoreDataLocally: false +// }; + }, + + handleBackClick: function (anEvent) { + MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'goBack'); + }, + + toggleShouldStoreDataLocally: function (anEvent) { +// this.setState({shouldStoreDataLocally: !this.state['shouldStoreDataLocally']}); + Clipperz.PM.DataModel.devicePreferences.setShouldStoreDataLocally(!Clipperz.PM.DataModel.devicePreferences.shouldStoreDataLocally()); + this.setState({}); + }, + + shouldStoreDataLocally: function () { + return Clipperz.PM.DataModel.devicePreferences.shouldStoreDataLocally(); + }, + + syncNow: function (anEvent) { + MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'synchronizeLocalData'); + }, + + //========================================================================= + + render: function () { + return React.DOM.div({className:'preferences'}, [ + React.DOM.div({className:'header'}, [ + React.DOM.div({className:'titleWrapper'}, React.DOM.div({className:'title'}, "Preferences")), + React.DOM.div({className:'backWrapper'}, React.DOM.a({className:'button back', onClick:this.handleBackClick}, "back")), + ]), + React.DOM.div({className:'content'}, [ + React.DOM.form(null, [ + React.DOM.div({className:'section'}, [ + React.DOM.h4(null, "Local storage"), + React.DOM.p(null, "Store you account data locally for offline viewing"), + new Clipperz.PM.UI.Components.Checkbox({'id':'shouldStoreLocally_checkbox', 'checked':this.shouldStoreDataLocally(), 'eventHandler':this.toggleShouldStoreDataLocally}), + this.shouldStoreDataLocally() ? React.DOM.div({className:'syncInfo'}, [ +// React.DOM.h5(null, "data were never synchronized before"), + React.DOM.a({className:'button', onClick:this.syncNow}, "Sync now") + ]) : null + ]) + ]) + ]), + React.DOM.div({className:'footer'}, [ + + ]) + ]); + } + + //========================================================================= +}); |