From 20bea94ab6b91c85b171dcf86baba0a64169d508 Mon Sep 17 00:00:00 2001 From: Giulio Cesare Solaroli Date: Fri, 30 Aug 2013 15:56:53 +0000 Subject: First release of /delta version --- (limited to 'frontend/delta/js/Clipperz/PM/UI') diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/CardDetail.js b/frontend/delta/js/Clipperz/PM/UI/Components/CardDetail.js new file mode 100644 index 0000000..df514a2 --- a/dev/null +++ b/frontend/delta/js/Clipperz/PM/UI/Components/CardDetail.js @@ -0,0 +1,142 @@ +/* + +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.CardDetail = React.createClass({ + + getDefaultProps: function () { + return { +// searchDelay: 0.3 + } + }, + + propTypes: { + card: React.PropTypes.object.isRequired + }, + + getInitialState: function () { + return { +// showSearch: false, +// searchTimer: null, + starred: false + }; + }, + + handleDirectLoginClick: function (aDirectLoginReference, anEvent) { + MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'runDirectLogin', {record:this.props.card['reference'], directLogin:aDirectLoginReference}); + }, + + //========================================================================= + + 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; + + if (aField['actionType'] == 'URL') { + actionLabel = "go"; + } else if (aField['actionType'] == 'PASSWORD') { + actionLabel = "locked"; + } else if (aField['actionType'] == 'EMAIL') { + actionLabel = "email"; + } else { + actionLabel = ""; + } + + return React.DOM.div({className:'listItem ' + aField['actionType']}, [ + 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:'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(); + }, + + handleStarClick: function (anEvent) { + this.setState({starred: !this.state['starred']}); + }, + + //========================================================================= + + render: function () { + var card = this.props.card; + 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:'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"), +// React.DOM.a({className:'save'}, "save") + + React.DOM.a({className:'cancel button'}, "failed"), + React.DOM.a({className:'save button'}, "done") +*/ + ]) + ]); + } + + //========================================================================= +}); diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/CardList.js b/frontend/delta/js/Clipperz/PM/UI/Components/CardList.js new file mode 100644 index 0000000..66d20f1 --- a/dev/null +++ b/frontend/delta/js/Clipperz/PM/UI/Components/CardList.js @@ -0,0 +1,161 @@ +/* + +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.CardList = React.createClass({ + + getDefaultProps: function () { + return { + selectedCard: null, + searchDelay: 0.3 + } + }, + + propTypes: { + searchDelay: React.PropTypes.number + }, + + getInitialState: function () { + return { + showSearch: false, + searchTimer: null, + searchText: '', +// passphrase: '', +// pin: '' + }; + }, + + //========================================================================= + + toggleSearch: function (anEvent) { + var showSearchBox; + + showSearchBox = !this.state.showSearch; + + this.setState({showSearch: showSearchBox}); + + if (showSearchBox) { + MochiKit.Async.callLater(0.1, MochiKit.Base.method(this, 'focusOnSearchField')); + } + }, + + updateSearchText: function (anEvent) { + var searchText; + + searchText = anEvent.target.value; +//console.log(">>> updateSearchText", searchText); + + if ((this.state['searchTimer'] != null) && (searchText != this.state['searchText'])) { + this.state['searchTimer'].cancel(); + } + + if (searchText != this.state['searchText']) { + this.state['searchText'] = searchText; + this.state['searchTimer'] = MochiKit.Async.callLater(this.props['searchDelay'], MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'searchCards', searchText); + } + }, + + focusOnSearchField: function () { +console.log("focusOnSearchField", this.refs['searchField']); + this.refs['searchField'].getDOMNode.focus(); + }, + + searchBox: function () { + var result; + + if (this.state.showSearch) { + result = React.DOM.div({className:'searchBox'}, [ + React.DOM.div(null, [ + React.DOM.input({type:'search', placeholder:"search", ref:'searchField', onChange:this.updateSearchText}) + ]) + ]); + } else { + result = null; + } + + return result; + }, + + //========================================================================= + + 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)}, [ + 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) { + MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'showRecord', aRecordReference); + }, + + cardListItems: function () { + var list; + var result; + + list = this.props['cardList']; + + if (typeof(list) != 'undefined') { + result = MochiKit.Base.map(MochiKit.Base.method(this, 'cardItem'), list); + } else { + result = null; + } + + return result; + }, + + //========================================================================= + + handleChange: function (anEvent) { +// var refs = this.refs; +// var refName = MochiKit.Base.filter(function (aRefName) { return refs[aRefName].getDOMNode() == anEvent.target}, MochiKit.Base.keys(this.refs))[0]; +// var newState = {}; +// +// newState[refName] = event.target.value; +// this.setState(newState); + }, + + //========================================================================= + + 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:'search ' + (this.state.showSearch ? 'selected' : ''), onClick:this.toggleSearch}, 'search'), + React.DOM.a({className:'settings'}, 'settings') + ]), +// this.searchBox() + ]), + this.searchBox(), + React.DOM.div({className:'content cardList'}, this.cardListItems()), + ]); + } + + //========================================================================= +}); diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/ErrorPage.js b/frontend/delta/js/Clipperz/PM/UI/Components/ErrorPage.js new file mode 100644 index 0000000..a1979ec --- a/dev/null +++ b/frontend/delta/js/Clipperz/PM/UI/Components/ErrorPage.js @@ -0,0 +1,46 @@ +/* + +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.ErrorPage = React.createClass({ + + getDefaultProps: function () { + return { + template: Clipperz.PM.UI.Components.PageTemplate + } + }, + + 'propTypes': { +// type: React.PropTypes.oneOf(['PERMANENT', 'TEMPORARY']), + message: React.PropTypes.string.isRequired, + template: React.PropTypes.func + }, + + + _render: function () { + return React.DOM.div({className:'error-message'}, this.props.message); + }, + + render: function () { + return new this.props.template({'innerComponent': this._render()}); + } +}); diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/LoginForm.js b/frontend/delta/js/Clipperz/PM/UI/Components/LoginForm.js new file mode 100644 index 0000000..2b5b4a4 --- a/dev/null +++ b/frontend/delta/js/Clipperz/PM/UI/Components/LoginForm.js @@ -0,0 +1,150 @@ +/* + +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.LoginForm = React.createClass({ + + getDefaultProps: function () { + return { + mode: 'CREDENTIALS', + isNewUserRegistrationAvailable: false, + disabled: false, + template: Clipperz.PM.UI.Components.PageTemplate + } + }, + + propTypes: { + mode: React.PropTypes.oneOf(['CREDENTIALS','PIN']), + isNewUserRegistrationAvailable: React.PropTypes.bool, + disabled: React.PropTypes.bool, + template: React.PropTypes.func + }, + + getInitialState: function () { + return { + username: '', + passphrase: '', + pin: '' + }; + }, + + //========================================================================= + + handleChange: function (anEvent) { + var refs = this.refs; + var refName = MochiKit.Base.filter(function (aRefName) { return refs[aRefName].getDOMNode() == anEvent.target}, MochiKit.Base.keys(this.refs))[0]; + var newState = {}; + + newState[refName] = event.target.value; + this.setState(newState); + }, + + //========================================================================= + + handleCredentialSubmit: function (event) { + event.preventDefault(); + + this.refs['passphrase'].getDOMNode().blur(); + + var credentials = { + 'username': this.refs['username'].getDOMNode().value, + 'passphrase': this.refs['passphrase'].getDOMNode().value + } + MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'doLogin', credentials); + }, + + handleRegistrationLinkClick: function (event) { + event.preventDefault(); + MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'showRegistrationForm'); + }, + + //------------------------------------------------------------------------- + + shouldEnableLoginButton: function () { + var result; + + return ( + ((this.state['username'] != '') && (this.state['passphrase'] != '')) + || + (this.state['pin'] != '') + ) && !this.props['disabled']; + }, + + + loginForm: function () { + registrationLink = React.DOM.div({'className':'registrationLink'}, [ + React.DOM.a({'onClick':this.handleRegistrationLinkClick}, "Need an account") + ]); + 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'}) + ]), + React.DOM.button({'type':'submit', 'disabled':!this.shouldEnableLoginButton(), 'className':'button'}, "login") + ]), + this.props.isNewUserRegistrationAvailable ? registrationLink : null + ]); + }, + + handlePINSubmit: function (event) { + event.preventDefault(); + + this.refs['pin'].getDOMNode().blur(); + + var credentials = { + pin: this.refs['pin'].getDOMNode().value + } + + MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'doLogin', credentials); + }, + + pinForm: function () { + return React.DOM.div({'className':'loginForm pin'},[ + React.DOM.form({onChange: this.handleChange, onSubmit:this.handlePINSubmit}, [ + React.DOM.div(null,[ + React.DOM.label({'for':'pin'}, "pin"), + React.DOM.input({'type':'text', 'name':'pin', 'ref':'pin', placeholder:"PIN", 'key':'pin', 'autocapitalize':'none'}) + ]), + React.DOM.button({'type':'submit', 'disabled':this.props.disabled, 'className':'button'}, "login") + ]) + ]); + }, + + setInitialFocus: function () { + if (this.props.mode == 'PIN') { + this.refs['pin'].getDOMNode().select(); + } else { + if (this.refs['username'].getDOMNode().value == '') { + this.refs['username'].getDOMNode().focus(); + } else{ + this.refs['passphrase'].getDOMNode().select(); + } + } + }, + + render: function() { + return new this.props.template({'innerComponent': this.props.mode == 'PIN' ? this.pinForm() : this.loginForm()}); + } +}); diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/Overlay.js b/frontend/delta/js/Clipperz/PM/UI/Components/Overlay.js new file mode 100644 index 0000000..cc4a06c --- a/dev/null +++ b/frontend/delta/js/Clipperz/PM/UI/Components/Overlay.js @@ -0,0 +1,122 @@ +/* + +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.Base.module('Clipperz.PM.UI.Components'); + +Clipperz.PM.UI.Components.Overlay = function(args) { + args = args || {}; + + this._defaultDelay = 2; + this._element = MochiKit.DOM.getElement('overlay'); + + return this; +} + +//============================================================================= + +Clipperz.Base.extend(Clipperz.PM.UI.Components.Overlay, Object, { + + //------------------------------------------------------------------------- + + 'toString': function () { + return "Clipperz.PM.UI.Components.Overlay component"; + }, + + 'element': function () { +// return MochiKit.DOM.getElement('overlay'); + return this._element; + }, + + 'getElement': function (aClass) { + return MochiKit.Selector.findChildElements(this.element(), ['.'+aClass])[0]; + }, + + //------------------------------------------------------------------------- + + 'show': function (aMessage) { + this.resetStatus(); + this.setMessage(aMessage); + MochiKit.DOM.removeElementClass(this.element(), 'ios-overlay-hide'); + MochiKit.DOM.addElementClass(this.element(), 'ios-overlay-show'); + }, + + 'done': function (aMessage, aDelayBeforeHiding) { + this.completed(this.showDoneIcon, aMessage, aDelayBeforeHiding); + }, + + 'failed': function (aMessage, aDelayBeforeHiding) { + this.completed(this.showFailIcon, aMessage, aDelayBeforeHiding); + }, + + //------------------------------------------------------------------------- + + 'resetStatus': function () { + MochiKit.Style.showElement(this.element()); + MochiKit.Style.showElement(this.getElement('spinner')); + MochiKit.Style.hideElement(this.getElement('done')); + MochiKit.Style.hideElement(this.getElement('failed')); + }, + + 'setMessage': function (aMessage) { + if (typeof(aMessage) != 'undefined') { + this.getElement('title').innerHTML = aMessage; + } + }, + + 'completed': function (aFunctionToShowResult, aMessage, aDelayBeforeHiding) { + var delay = aDelayBeforeHiding || this.defaultDelay(); + + 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()); + }, + + 'hideSpinner': function () { + MochiKit.Style.hideElement(this.getElement('spinner')); + }, + + 'showDoneIcon': function () { + MochiKit.Style.showElement(this.getElement('done')); + }, + + 'showFailIcon': function () { + MochiKit.Style.showElement(this.getElement('failed')); + }, + + //------------------------------------------------------------------------- + + 'defaultDelay': function () { + return this._defaultDelay; + }, + + //------------------------------------------------------------------------- + __syntaxFix__: "syntax fix" +}); diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/PageTemplate.js b/frontend/delta/js/Clipperz/PM/UI/Components/PageTemplate.js new file mode 100644 index 0000000..9b7c748 --- a/dev/null +++ b/frontend/delta/js/Clipperz/PM/UI/Components/PageTemplate.js @@ -0,0 +1,33 @@ +/* + +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.PageTemplate = React.createClass({ + render: function() { + return React.DOM.div(null, [ + React.DOM.div({'className': 'header'}, [ + React.DOM.h1(null, "clipperz") + ]), + React.DOM.div({'className': 'content'}, this.props.innerComponent) + ]) + } +}); diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/RegistrationWizard.js b/frontend/delta/js/Clipperz/PM/UI/Components/RegistrationWizard.js new file mode 100644 index 0000000..051dcc5 --- a/dev/null +++ b/frontend/delta/js/Clipperz/PM/UI/Components/RegistrationWizard.js @@ -0,0 +1,240 @@ +/* + +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.RegistrationWizard = React.createClass({ + + getDefaultProps: function () { + return { + steps: [ + {name:'CREDENTIALS', label:'registration', _label:'credentials', description:"Choose your credentails"}, + {name:'PASSWORD_VERIFICATION', label:'registration', _label:'verify', description:"Verify your passphrase"}, + {name:'TERMS_OF_SERVICE', label:'registration', _label:'terms', description:"Check our terms of service"} + ], + disabled: false, + template: Clipperz.PM.UI.Components.PageTemplate + } + }, + + getInitialState: function () { + return { + currentStep: this.props['steps'][0]['name'], + username: '', + passphrase: '', + verify_passphrase: '', + no_password_recovery: false, + agree_terms_of_service: false + }; + }, + + 'propTypes': { +// steps: React.PropTypes.array, + disabled: React.PropTypes.bool, + template: React.PropTypes.func + }, + + //========================================================================= + + currentStepIndex: function () { + return this.indexOfStepNamed(this.state['currentStep']); + }, + + indexOfStepNamed: function (aStepName) { + var stepConfiguration; + var result; + + stepConfiguration = this.props['steps'].filter(function (aConfig) { return aConfig['name'] == aStepName})[0]; + result = this.props['steps'].indexOf(stepConfiguration); + return result; + }, + + //========================================================================= + + statusClassForStep: function (aStep) { + var currentStepIndex = this.currentStepIndex(); + var stepIndex = this.indexOfStepNamed(aStep['name']); + var result; + + if (stepIndex < currentStepIndex) { + result = 'left'; + } else if (stepIndex == currentStepIndex) { + result = 'center'; + } else { + result = 'right'; + } + + return result; + }, + + //========================================================================= + + handleBackClick: function (anEvent) { + var nextStep; + anEvent.preventDefault(); + + if (this.currentStepIndex() > 0) { + nextStep = this.props['steps'][this.currentStepIndex() - 1]; + this.setState({currentStep: nextStep['name']}); + } else { + MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'goBack'); + } + }, + + handleForwardClick: function (anEvent) { + var nextStep; + anEvent.preventDefault(); + + if (this.canMoveForward()) { + + if (this.currentStepIndex() < this.props['steps'].length - 1) { + nextStep = this.props['steps'][this.currentStepIndex() + 1]; + this.setState({currentStep: nextStep['name']}); + } else { + MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'registerNewUser', { + username: this.state['username'], + passphrase: this.state['passphrase'] + }) + } + } + }, + + //------------------------------------------------------------------------- + + canMoveForward: function () { + var result; + var currentStep; + + result = false; + currentStep = this.state['currentStep']; + if (currentStep == 'CREDENTIALS') { + result = ((this.state['username'] != '') && (this.state['passphrase'] != '')); + } else if (currentStep == 'PASSWORD_VERIFICATION') { + result = (this.state['passphrase'] == this.state['verify_passphrase']); + } else if (currentStep == 'TERMS_OF_SERVICE') { + result = (this.state['no_password_recovery'] && this.state['agree_terms_of_service']); + } + + return result && !this.props['disabled']; + }, + + //========================================================================= + + handleChange: function (anEvent) { + var refs = this.refs; + var refName = MochiKit.Base.filter(function (aRefName) { return refs[aRefName].getDOMNode() == anEvent.target}, MochiKit.Base.keys(this.refs))[0]; + var newState = {}; + + if ((event.target.type == 'checkbox') || (event.target.type == 'radio')) { + newState[refName] = event.target.checked; + } else { + newState[refName] = event.target.value; + } + this.setState(newState); + }, + + //========================================================================= + + renderIndexStep: function (aStep) { + return React.DOM.div({'className':'stepIndexItem ' + this.statusClassForStep(aStep)}, '.'); + }, + + renderButtons: function () { + return [ + React.DOM.a({className:'back button step_' + (this.currentStepIndex() - 1), onClick:this.handleBackClick}, '<<'), + React.DOM.a({className:'forward button step_' + (this.currentStepIndex() + 1) + ' ' + (this.canMoveForward() ? 'enabled' : 'disabled'), onClick:this.handleForwardClick}, '>>') + ]; + }, + + render_CREDENTIALS: function () { + return 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'/*, value:this.state.username*/}), + React.DOM.label({'for':'passphrase'}, "passphrase"), + React.DOM.input({'type':'password', 'name':'passphrase', 'ref':'passphrase', 'placeholder':"passphrase", 'key':'passphrase'/*, value:this.state.passphrase*/}) + ]); + }, + + render_PASSWORD_VERIFICATION: function () { + return React.DOM.div(null,[ + React.DOM.label({'for':'verify_passphrase'}, "passphrase"), + React.DOM.input({'type':'password', 'name':'verify_passphrase', 'ref':'verify_passphrase', 'placeholder':"verify passphrase", 'key':'verify_passphrase'}) + ]); + }, + + render_TERMS_OF_SERVICE: function () { + return React.DOM.div(null, [ + React.DOM.div({className:'checkboxBlock'}, [ + React.DOM.label({'for':'no_password_recovery'}, "I understand that Clipperz will not be able to recover a lost passphrase."), + React.DOM.input({'type':'checkbox', 'name':'no_password_recovery', 'ref':'no_password_recovery', 'key':'no_password_recovery'}), + React.DOM.p(null, "I understand that Clipperz will not be able to recover a lost passphrase.") + ]), + React.DOM.div({className:'checkboxBlock'}, [ + React.DOM.label({'for':'agree_terms_of_service'}, "I have read and agreed to the Terms of Service."), + React.DOM.input({'type':'checkbox', 'name':'agree_terms_of_service', 'ref':'agree_terms_of_service', 'key':'agree_terms_of_service'}), + React.DOM.p(null, [ + "I have read and agreed to the ", + React.DOM.a({href:'https://clipperz.com/terms_service/', target:'_blank'}, "Terms of Service.") + ]) + ]) + ]); + }, + + renderStep: function (aStep) { + return React.DOM.div({'className':'step' + ' ' + aStep['name'] + ' ' + this.statusClassForStep(aStep) + ' step_' + this.currentStepIndex()}, [ + React.DOM.h1(null, aStep['label']), + React.DOM.p(null, aStep['description']), + this['render_' + aStep['name']].apply(), + React.DOM.div({'className':'stepIndex'}, MochiKit.Base.map(this.renderIndexStep, this.props['steps'])), + React.DOM.div({'className':'buttons'}, this.renderButtons()) + ]); + }, + + _render: function () { + return React.DOM.div({'className':'registrationForm'},[ + React.DOM.form({onChange: this.handleChange}, [ + React.DOM.div({'className':'steps'}, MochiKit.Base.map(this.renderStep, this.props['steps'])) + ]) + ]); + }, + + render: function () { + return new this.props.template({'innerComponent': this._render()}); + }, + + //========================================================================= + + setInitialFocus: function () { + this.refs['username'].getDOMNode().focus(); + }, + + componentDidUpdate: function (prevProps, prevState, rootNode) { + if (prevState['currentStep'] != this.state['currentStep']) { + if (this.state['currentStep'] == 'CREDENTIALS') { + this.refs['passphrase'].getDOMNode().select(); + } else if (this.state['currentStep'] == 'PASSWORD_VERIFICATION') { + this.refs['verify_passphrase'].getDOMNode().select(); + } + } + } + + //========================================================================= +}); diff --git a/frontend/delta/js/Clipperz/PM/UI/DirectLoginController.js b/frontend/delta/js/Clipperz/PM/UI/DirectLoginController.js new file mode 100644 index 0000000..d9dfe6d --- a/dev/null +++ b/frontend/delta/js/Clipperz/PM/UI/DirectLoginController.js @@ -0,0 +1,256 @@ +/* + +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.Base.module('Clipperz.PM.UI'); + +Clipperz.PM.UI.DirectLoginRunner = function(args) { + this._directLogin = args['directLogin'] || Clipperz.Base.exception.raise('MandatoryParameter'); + this._target = Clipperz.PM.Crypto.randomKey(); + + return this; +} + +MochiKit.Base.update(Clipperz.PM.UI.DirectLoginRunner.prototype, { + + 'toString': function() { + return "Clipperz.PM.UI.DirectLoginRunner"; + }, + + //----------------------------------------------------------------------------- + + 'directLogin': function () { + return this._directLogin; + }, + + //----------------------------------------------------------------------------- + + 'target': function () { + return this._target; + }, + + //============================================================================= + + 'setWindowTitle': function (aWindow, aTitle) { + aWindow.document.title = aTitle; + }, + + 'setWindowBody': function (aWindow, anHTML) { + aWindow.document.body.innerHTML = anHTML; + }, + + //============================================================================= + + 'initialWindowSetup': function (aWindow) { + this.setWindowTitle(aWindow, "Loading Clipperz Direct Login"); + this.setWindowBody (aWindow, MochiKit.DOM.toHTML(MochiKit.DOM.H3("Loading Clipperz Direct Login ..."))); + }, + + //----------------------------------------------------------------------------- + + 'updateWindowWithDirectLoginLabel': function (aWindow, aLabel) { + var titleText; + var bodyText; + + titleText = "Loading '__label__' Direct Login".replace(/__label__/, aLabel) + bodyText = "Loading '__label__' Direct Login... ".replace(/__label__/, aLabel) + + this.setWindowTitle(aWindow, titleText); + this.setWindowBody (aWindow, MochiKit.DOM.toHTML(MochiKit.DOM.H3(bodyText))); + }, + + //----------------------------------------------------------------------------- + + 'updateWindowWithHTMLContent': function (aWindow, anHtml) { + this.setWindowBody(aWindow, anHtml); + }, + + //============================================================================= + + 'submitLoginForm': function(aWindow, aSubmitFunction) { + MochiKit.DOM.withWindow(aWindow, MochiKit.Base.bind(function () { + var formElement; + var submitButtons; + + formElement = MochiKit.DOM.getElement('directLoginForm'); + + submitButtons = MochiKit.Base.filter(function(anInputElement) { + return ((anInputElement.tagName.toLowerCase() == 'input') && (anInputElement.getAttribute('type').toLowerCase() == 'submit')); + }, formElement.elements); + + if (submitButtons.length == 0) { + if (typeof(formElement.submit) == 'function') { + formElement.submit(); + } else { + aSubmitFunction.apply(formElement); + } +/* + var formSubmitFunction; + + formSubmitFunction = MochiKit.Base.method(formElement, 'submit'); + if (Clipperz_IEisBroken == true) { + formElement.submit(); + } else { + formSubmitFunction(); + } +*/ + } else { + submitButtons[0].click(); + } + }, this)); + }, + + //------------------------------------------------------------------------- + + 'runSubmitFormDirectLogin': function (aWindow, someAttributes) { + var html; + var formElement; + var submitFunction; + + formElement = MochiKit.DOM.FORM({ + 'id':'directLoginForm', + 'method':someAttributes['formAttributes']['method'], + 'action':someAttributes['formAttributes']['action'] + }); + + submitFunction = formElement.submit; + + MochiKit.DOM.appendChildNodes(formElement, MochiKit.Base.map(function (anInputAttributes) { + return MochiKit.DOM.INPUT({'type':'hidden', 'name':anInputAttributes[0], 'value':anInputAttributes[1]}); + }, MochiKit.Base.items(someAttributes['inputValues']))); + + html = ''; + html += '

Loading ' + someAttributes['label'] + ' ...

'; + html += MochiKit.DOM.appendChildNodes(MochiKit.DOM.DIV(), MochiKit.DOM.appendChildNodes(MochiKit.DOM.DIV({style:'display:none; visibility:hidden;'}), formElement)).innerHTML; + + this.updateWindowWithHTMLContent(aWindow, html); + this.submitLoginForm(aWindow, submitFunction); + }, + + //------------------------------------------------------------------------- + + 'runHttpAuthDirectLogin': function(aWindow, someAttributes) { + var completeUrl; + var url; + + url = someAttributes['inputValues']['url']; + + if (/^https?\:\/\//.test(url) == false) { + url = 'http://' + url; + } + + if (Clipperz_IEisBroken === true) { + completeUrl = url; + } else { + var username; + var password; + + username = someAttributes['inputValues']['username']; + password = someAttributes['inputValues']['password']; + /(^https?\:\/\/)?(.*)/.test(url); + + completeUrl = RegExp.$1 + username + ':' + password + '@' + RegExp.$2; + } + + window.open(completeUrl, this.target()); + }, + + //============================================================================= + + 'runDirectLogin': function (aWindow) { + var deferredResult; + + deferredResult = new Clipperz.Async.Deferred("DirectLoginRunner.openDirectLogin", {trace:false}); + deferredResult.addMethod(this, 'initialWindowSetup', aWindow); + deferredResult.addMethod(this.directLogin(), 'label'); + deferredResult.addMethod(this, 'updateWindowWithDirectLoginLabel', aWindow); + deferredResult.collectResults({ + 'type': MochiKit.Base.method(this.directLogin(), 'type'), + 'label': MochiKit.Base.method(this.directLogin(), 'label'), + 'formAttributes': MochiKit.Base.method(this.directLogin(), 'formAttributes'), + 'inputValues': MochiKit.Base.method(this.directLogin(), 'inputValues') + }); + deferredResult.addCallback(MochiKit.Base.bind(function (someAttributes) { + switch (someAttributes['type']) { + case 'http_auth': + this.runHttpAuthDirectLogin(aWindow, someAttributes); + break; + case 'simple_url': + this.runSimpleUrlDirectLogin(aWindow, someAttributes); + break; + default: + this.runSubmitFormDirectLogin(aWindow, someAttributes); + break; + } + }, this)); + deferredResult.callback(); + + return deferredResult; + }, + + //============================================================================= + + 'run': function () { + var newWindow; + + newWindow = window.open(Clipperz.PM.Strings.getValue('directLoginJumpPageUrl'), this.target()); + + return this.runDirectLogin(newWindow); + }, + + //============================================================================= + + 'test': function () { + var iFrame; + var newWindow; + + iFrame = MochiKit.DOM.createDOM('iframe'); + MochiKit.DOM.appendChildNodes(MochiKit.DOM.currentDocument().body, iFrame); + + newWindow = iFrame.contentWindow; + + return this.runDirectLogin(newWindow); + }, + + //============================================================================= + __syntaxFix__: "syntax fix" +}); + +//----------------------------------------------------------------------------- + +Clipperz.PM.UI.DirectLoginRunner.openDirectLogin = function (aDirectLogin) { + var runner; + + runner = new Clipperz.PM.UI.DirectLoginRunner({directLogin:aDirectLogin}); + return runner.run(); +}; + +//----------------------------------------------------------------------------- + +Clipperz.PM.UI.DirectLoginRunner.testDirectLogin = function (aDirectLogin) { + var runner; + + runner = new Clipperz.PM.UI.DirectLoginRunner({directLogin:aDirectLogin}); + return runner.test(); +}; + +//----------------------------------------------------------------------------- diff --git a/frontend/delta/js/Clipperz/PM/UI/MainController.js b/frontend/delta/js/Clipperz/PM/UI/MainController.js new file mode 100644 index 0000000..da7540e --- a/dev/null +++ b/frontend/delta/js/Clipperz/PM/UI/MainController.js @@ -0,0 +1,491 @@ +/* + +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.Base.module('Clipperz.PM.UI'); + +Clipperz.PM.UI.MainController = function() { + var pages; + + this._proxy = null; + this._user = null; + this._filter = ''; + +// this._currentPage = 'loadingPage'; + + this._pageStack = ['loadingPage']; + this._overlay = new Clipperz.PM.UI.Components.Overlay(); + pages = { + 'loginPage': new Clipperz.PM.UI.Components.LoginForm(), + 'registrationPage': new Clipperz.PM.UI.Components.RegistrationWizard(), + 'cardListPage': new Clipperz.PM.UI.Components.CardList(), + 'cardDetailPage': new Clipperz.PM.UI.Components.CardDetail({card: {}}), + 'errorPage': new Clipperz.PM.UI.Components.ErrorPage({message:''}) + }; + + MochiKit.Base.map(function (anId) {React.renderComponent(pages[anId], MochiKit.DOM.getElement(anId))}, MochiKit.Base.keys(pages)); + this._pages = pages; + this.registerForNotificationCenterEvents(); + + return this; +} + +MochiKit.Base.update(Clipperz.PM.UI.MainController.prototype, { + + toString: function () { + return "Clipperz.PM.UI.MainController"; + }, + + //========================================================================= + + overlay: function () { + return this._overlay; + }, + + loginForm: function () { + return this._loginForm; + }, + + registrationWizard: function () { + return this._registrationWizard; + }, + + //========================================================================= + + isOnline: function() { + return navigator.onLine; + }, + + hasLocalData: function() { + return false; + }, + + loginMode: function () { + // PIN is set using this command: + // Clipperz.PM.PIN.setCredentialsWithPIN('1234', {'username':'joe', 'passphrase':'clipperz'}); + + return Clipperz.PM.PIN.isSet() ? 'PIN' : 'CREDENTIALS'; + }, + + //========================================================================= + + pages: function () { + return this._pages; + }, + + pageStack: function () { + return this._pageStack; + }, + + //========================================================================= + + selectInitialProxy: function () { + if (this.isOnline()) { + this._proxy = Clipperz.PM.Proxy.defaultProxy; + } else { + if (this.hasLocalData()) { + this._proxy = new Clipperz.PM.Proxy.Offline({dataStore: new Clipperz.PM.Proxy.Offline.LocalStorageDataStore(), shouldPayTolls:false}); + } else { + this.showOfflineError(); + } + } + }, + + proxy: function () { + return this._proxy; + }, + + //========================================================================= + + registerForNotificationCenterEvents: function () { + var events = ['doLogin', 'registerNewUser', 'showRegistrationForm', 'goBack', 'showRecord', 'searchCards', 'runDirectLogin']; + var self = this; + + MochiKit.Base.map(function (anEvent) { + MochiKit.Signal.connect(Clipperz.Signal.NotificationCenter, anEvent, MochiKit.Base.method(self, anEvent)); + }, events); + +// MochiKit.Signal.connect(window, 'onpopstate', MochiKit.Base.method(this, 'historyGoBack')); + MochiKit.Signal.connect(window, 'onbeforeunload', MochiKit.Base.method(this, 'shouldExitApp')); + }, + + //------------------------------------------------------------------------- + + run: function (parameters) { + var shouldShowRegistrationForm; + + this.selectInitialProxy(); + shouldShowRegistrationForm = parameters['shouldShowRegistrationForm'] && this.proxy().canRegisterNewUsers(); + this.pages()['loginPage'].setProps({'mode':this.loginMode(), 'isNewUserRegistrationAvailable': this.proxy().canRegisterNewUsers()}); + + if (shouldShowRegistrationForm) { + this.showRegistrationForm(); + } else { + this.showLoginForm(); + } + this.overlay().done("", 0.5); + }, + + //------------------------------------------------------------------------- + + showLoginForm: function () { + var loginFormPage; + + loginFormPage = this.pages()['loginPage']; + loginFormPage.setProps({'mode':this.loginMode(), 'isNewUserRegistrationAvailable': this.proxy().canRegisterNewUsers()}); + this.moveInPage(this.currentPage(), 'loginPage'); + MochiKit.Async.callLater(0.5, MochiKit.Base.method(loginFormPage, 'setInitialFocus')); + }, + + showRegistrationForm: function () { + var currentPage; + var registrationPage; + + currentPage = this.currentPage(); + registrationPage = this.pages()['registrationPage']; + this.setCurrentPage('loginPage'); + registrationPage.setProps({}); + this.moveInPage(currentPage, 'registrationPage'); + MochiKit.Async.callLater(0.5, MochiKit.Base.method(registrationPage, 'setInitialFocus')); + }, + + //========================================================================= + + doLogin: function (event) { + var credentials; + var getPassphraseDelegate; + var user; + + user = null; + + this.overlay().show("logging in"); + this.pages()['loginPage'].setProps({disabled:true}); + + if ('pin' in event) { + credentials = Clipperz.PM.PIN.credentialsWithPIN(event['pin']); + } else { + credentials = event; + } + getPassphraseDelegate = MochiKit.Base.partial(MochiKit.Async.succeed, credentials.passphrase); + user = new Clipperz.PM.DataModel.User({'username':credentials.username, 'getPassphraseFunction':getPassphraseDelegate}); + + deferredResult = new Clipperz.Async.Deferred('MainController.doLogin', {trace:false}); + deferredResult.addCallback(MochiKit.Async.wait, 0.1); + deferredResult.addMethod(Clipperz.Crypto.PRNG.defaultRandomGenerator(), 'deferredEntropyCollection'); + deferredResult.addMethod(user, 'login'); + deferredResult.addMethod(Clipperz.PM.PIN, 'resetFailedAttemptCount'); + deferredResult.addMethod(this, 'setUser', user); + +// deferredResult.addMethod(this, 'setupApplication'); + deferredResult.addMethod(this, 'runApplication'); + deferredResult.addMethod(this.overlay(), 'done', "", 1); + deferredResult.addErrback(MochiKit.Base.method(this, 'genericErrorHandler', event)); + deferredResult.addErrback(MochiKit.Base.bind(function (anEvent, anError) { + if (anError['isPermanent'] != true) { + this.pages()['loginPage'].setProps({disabled:false, 'mode':this.loginMode()}); + this.pages()['loginPage'].setInitialFocus(); + } + return anError; + }, this, event)) + deferredResult.callback(); + + return deferredResult; + }, + + //------------------------------------------------------------------------- + + registerNewUser: function (credentials) { + var deferredResult; + + this.overlay().show("creating user"); + + this.pages()['registrationPage'].setProps({disabled:true}); + deferredResult = new Clipperz.Async.Deferred('MainController.registerNewUser', {trace:false}); + deferredResult.addCallback(Clipperz.PM.DataModel.User.registerNewAccount, + credentials['username'], + MochiKit.Base.partial(MochiKit.Async.succeed, credentials['passphrase']) + ); + deferredResult.addMethod(this, 'doLogin', credentials); + deferredResult.addErrback(MochiKit.Base.method(this, 'genericErrorHandler', event)); + deferredResult.addErrback(MochiKit.Base.bind(function (anError) { + if (anError['isPermanent'] != true) { + this.pages()['registrationPage'].setProps({disabled:false}); + this.pages()['registrationPage'].setInitialFocus(); + } + return anError; + }, this)); + + deferredResult.callback(); + + return deferredResult; + + }, + + //------------------------------------------------------------------------- + + user: function () { + return this._user; + }, + + setUser: function (aUser) { + this._user = aUser; + return this._user; + }, + + //========================================================================= + + allCardInfo: function () { + var deferredResult; + var cardInfo; + + cardInfo = { + '_rowObject': MochiKit.Async.succeed, + '_reference': MochiKit.Base.methodcaller('reference'), + '_searchableContent': MochiKit.Base.methodcaller('searchableContent'), + 'label': MochiKit.Base.methodcaller('label'), + 'favicon': MochiKit.Base.methodcaller('favicon') + }; + + deferredResult = new Clipperz.Async.Deferred('MainController.allCardInfo', {trace:false}); + deferredResult.addMethod(this.user(), 'getRecords'); + deferredResult.addCallback(MochiKit.Base.map, Clipperz.Async.collectResults("CardList.value - collectResults", cardInfo, {trace:false})); + deferredResult.addCallback(Clipperz.Async.collectAll); + deferredResult.callback(); + + return deferredResult; + }, + + filterCards: function (someCardInfo) { + var filter; + var filterRegExp; + var result; + + filter = this.filter().replace(/[^A-Za-z0-9]/g, "\\$&"); + filterRegExp = new RegExp(filter, "i"); + result = MochiKit.Base.filter(function (aCardInfo) { return filterRegExp.test(aCardInfo['_searchableContent'])}, someCardInfo); + + return result; + }, + + sortCards: function (someCardInfo) { + return someCardInfo.sort(Clipperz.Base.caseInsensitiveKeyComparator('label')); + }, + + showRecordList: function () { + var deferredResult; + + deferredResult = new Clipperz.Async.Deferred('MainController.showRecordList', {trace:false}); + deferredResult.addMethod(this, 'allCardInfo'); + deferredResult.addMethod(this, 'filterCards'); + deferredResult.addMethod(this, 'sortCards'); + deferredResult.addCallback(MochiKit.Base.bind(function (someRecordInfo) { + this.pages()['cardListPage'].setProps({cardList: someRecordInfo}); + }, this)); + deferredResult.callback(); + + return deferredResult; + }, + + filter: function () { + return this._filter; + }, + + setFilter: function (aValue) { + this._filter = aValue; + }, + + searchCards: function (someParameters) { +//console.log("SEARCH CARDS", someParameters); + this.setFilter(someParameters); + this.showRecordList(); + }, + + //========================================================================= + + runApplication: function () { + MochiKit.Signal.connect(window, 'onpopstate', MochiKit.Base.method(this, 'historyGoBack')); + this.moveInPage(this.currentPage(), 'cardListPage'); + return this.showRecordList(); + }, + + showRecord: function (aRecordReference) { +//console.log("Show Record", aRecordReference); + var deferredResult; + + this.pages()['cardListPage'].setProps({selectedCard:aRecordReference}); + deferredResult = new Clipperz.Async.Deferred('MainController.runApplication', {trace:false}); +// deferredResult.addMethod(this.user(), 'getRecord', aRecordReference['_reference']); + deferredResult.addMethod(this.user(), 'getRecord', aRecordReference); + deferredResult.addMethodcaller('content'); + deferredResult.addCallback(MochiKit.Base.bind(function (aCard) { +//console.log("CARD DETAILS", aCard); + this.pages()['cardDetailPage'].setProps({card: aCard}); + this.pages()['cardListPage'].setProps({selectedCard: null}); + }, this)); + deferredResult.addMethod(this, 'moveInPage', this.currentPage(), 'cardDetailPage', true); + deferredResult.callback(); + + return deferredResult; + }, + + runDirectLogin: function (someParameters) { +console.log("RUN DIRECT LOGIN", someParameters); + var deferredResult; + +// this.pages()['cardListPage'].setProps({selectedCard:aRecordReference}); + deferredResult = new Clipperz.Async.Deferred('MainController.runDirectLogin', {trace:false}); +// deferredResult.addMethod(this.user(), 'getRecord', aRecordReference['_reference']); + deferredResult.addMethod(this.user(), 'getRecord', someParameters['record']); + deferredResult.addMethodcaller('directLoginWithReference', someParameters['directLogin']); + deferredResult.addCallback(Clipperz.PM.UI.DirectLoginRunner.openDirectLogin); + deferredResult.callback(); + + return deferredResult; + }, + + shouldExitApp: function (anEvent) { +console.log("SHOULD EXIT APP"); + anEvent.preventDefault(); + anEvent.stopPropagation(); + }, + + //========================================================================= + + genericErrorHandler: function (anEvent, anError) { + var errorMessage; + var result; + + result = anError; + errorMessage = "login failed"; + + if (anError['isPermanent'] === true) { + this.pages()['errorPage'].setProps({message:anError.message}); + this.moveInPage(this.currentPage(), 'errorPage'); + errorMessage = "failure"; + } else { + if ('pin' in anEvent) { + errorCount = Clipperz.PM.PIN.recordFailedAttempt(); + if (errorCount == -1) { + errorMessage = "PIN resetted"; + } + } + } + this.overlay().failed(errorMessage, 1); + + return result; + }, + + //========================================================================= + + slidePage: function (fromPage, toPage, direction) { + var fromPosition; + var toPosition; + + if (direction == "LEFT") { + fromPosition = 'right'; + toPosition = 'left' + } else { + fromPosition = 'left'; + toPosition = 'right' + } + + MochiKit.DOM.addElementClass(fromPage, toPosition + ' transition'); + + MochiKit.DOM.addElementClass(toPage, fromPosition); + MochiKit.DOM.removeElementClass(toPage, toPosition); + MochiKit.DOM.addElementClass(toPage, 'transition'); + MochiKit.Async.callLater(0.1, function () { + MochiKit.DOM.removeElementClass(toPage, fromPosition); + }) + + MochiKit.Async.callLater(0.5, function () { + MochiKit.DOM.removeElementClass(fromPage, 'transition'); + MochiKit.DOM.removeElementClass(toPage, 'transition'); + }) + }, + + rotateInPage: function (fromPage, toPage) { + // Broken! :( + MochiKit.DOM.addElementClass(MochiKit.DOM.getElement('mainDiv'), 'show-right'); + }, + + //......................................................................... + + goBack: function () { + var fromPage; + var toPage; + + fromPage = this.pageStack().shift(); + toPage = this.currentPage(); + this.pages()[toPage].setProps({}); + this.moveOutPage(fromPage, toPage); + }, + + historyGoBack: function (anEvent) { + anEvent.preventDefault(); + anEvent.stopPropagation(); + this.goBack(); + }, + + currentPage: function () { + return this.pageStack()[0]; + }, + + setCurrentPage: function (aPage) { + this.pageStack().unshift(aPage); + }, + + moveInPage: function (fromPage, toPage, addToHistory) { + var shouldAddItemToHistory; + + shouldAddItemToHistory = typeof(addToHistory) == 'undefined' ? false : addToHistory; + + this.slidePage(MochiKit.DOM.getElement(fromPage), MochiKit.DOM.getElement(toPage), 'LEFT'); + this.setCurrentPage(toPage); + + if (shouldAddItemToHistory) { +//console.log("ADD ITEM TO HISTORY"); +//console.log("ADD ITEM TO HISTORY - window", window); +//console.log("ADD ITEM TO HISTORY - window.history", window.history); + window.history.pushState({'fromPage': fromPage, 'toPage': toPage}); +//# window.history.pushState(); +//console.log("ADDED ITEM TO HISTORY"); + } else { +//console.log("Skip HISTORY"); + } + }, + + moveOutPage: function (fromPage, toPage) { + this.slidePage(MochiKit.DOM.getElement(fromPage), MochiKit.DOM.getElement(toPage), 'RIGHT'); + this.setCurrentPage(toPage); + }, + + //========================================================================= +/* + wrongAppVersion: function (anError) { +// this.pages()['errorPage'].setProps({message:anError.message}); +// this.moveInPage('errorPage', this.currentPage()); + }, +*/ + //========================================================================= + __syntaxFix__: "syntax fix" +}); -- cgit v0.9.0.2