1
0
mirror of http://git.whoc.org.uk/git/password-manager.git synced 2025-10-30 10:57:35 +01:00

Reviewed and improved export feature

This commit is contained in:
Giulio Cesare Solaroli
2015-05-07 16:23:57 +02:00
parent b812ea4efb
commit e329f6926a
18 changed files with 1195 additions and 474 deletions

View File

@@ -30,18 +30,77 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataExportClass = React.createClass({
// featureSet: React.PropTypes.oneOf(['FULL', 'EXPIRED', 'TRIAL']).isRequired,
// 'level': React.PropTypes.oneOf(['hide', 'info', 'warning', 'error']).isRequired
},
/*
jsonExport: function () {
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'export', 'json');
},
htmlExport: function () {
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'export', 'html');
},
*/
isFeatureEnabled: function (aValue) {
return (this.props['features'].indexOf(aValue) > -1);
},
handleDownloadOfflineCopyLink: function (anEvent) {
if (this.isFeatureEnabled('OFFLINE_COPY')) {
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'downloadOfflineCopy');
}
},
handleExportLink: function () {
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'downloadExport');
},
//=========================================================================
render: function () {
return React.DOM.div({className:'extraFeature devicePIN'}, [
React.DOM.h1({}, "Export"),
React.DOM.p({'className': 'link', 'onClick': MochiKit.Base.method(this, function(){
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'export','json');
})}, "JSON"),
React.DOM.p({'className': 'link', 'onClick': MochiKit.Base.method(this, function(){
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'export','printable');
})}, "Printable version")
React.DOM.div({'className': 'content'}, [
React.DOM.ul({}, [
React.DOM.li({}, [
React.DOM.h3({}, "Offline copy"),
React.DOM.div({'className':'description'}, [
React.DOM.p({}, "Download a read-only portable version of Clipperz. Very convenient when no Internet connection is available."),
React.DOM.p({}, "An offline copy is just a single HTML file that contains both the whole Clipperz web application and your encrypted data."),
React.DOM.p({}, "It is as secure as the hosted Clipperz service since they both share the same code and security architecture.")
]),
React.DOM.a({'className':'button', 'onClick':this.handleDownloadOfflineCopyLink}, "download offline copy")
]),
React.DOM.li({}, [
React.DOM.h3({}, "HTML + JSON"),
React.DOM.div({'className':'description'}, [
React.DOM.p({}, "Download a printer-friendly HTML file that lists the content of all your cards."),
React.DOM.p({}, "This same file also contains all your data in JSON format."),
React.DOM.p({}, "Beware: all data are unencrypted! Therefore make sure to properly store and manage this file.")
]),
React.DOM.a({'className':'button', 'onClick':this.handleExportLink}, "download HTML+JSON")
]),
/*
React.DOM.li({}, [
React.DOM.h3({}, "Printing"),
React.DOM.div({'className':'description'}, [
React.DOM.p({}, "Click on the button below to open a new window displaying all your cards in a printable format."),
React.DOM.p({}, "If you are going to print for backup purposes, please consider the safer option provided by the “offline copy”.")
]),
React.DOM.a({'className':'button', 'onClick':this.htmlExport}, "HTML")
]),
React.DOM.li({}, [
React.DOM.h3({}, "Exporting to JSON"),
React.DOM.div({'className':'description'}, [
React.DOM.p({}, "JSON enables a “lossless” export of your cards. All the information will be preserved, including direct login configurations."),
React.DOM.p({}, "This custom format its quite convenient if you need to move some of all of your cards to a different Clipperz account. Or if you want to restore a card that has been accidentally deleted."),
React.DOM.p({}, "Click on the button below to start the export process.")
]),
React.DOM.a({'className':'button', 'onClick':this.jsonExport}, "JSON"),
])
*/
])
])
]);
},

View File

@@ -78,19 +78,21 @@ Clipperz.PM.UI.Components.ExtraFeatures.DeleteAccountClass = React.createClass({
return React.DOM.div({className:'extraFeature deleteAccount'}, [
React.DOM.h1({}, "Delete Account"),
React.DOM.form({'key':'form', 'className':'deleteAccountForm', 'onChange': this.handleFormChange, 'onSubmit':this.handleDeleteAccount}, [
React.DOM.div({'key':'fields'},[
React.DOM.label({'key':'username-label', 'htmlFor' :'name'}, "username"),
React.DOM.input({'key':'username', 'className':this.state['username'], 'type':'text', 'name':'name', 'ref':'username', 'placeholder':"username", 'autoCapitalize':'none'}),
React.DOM.label({'key':'passphrase-label', 'autoFocus': 'true', 'htmlFor' :'passphrase'}, "passphrase"),
React.DOM.input({'key':'passphrase', 'className':this.state['passphrase'], 'type':'password', 'name':'passphrase', 'ref':'passphrase', 'placeholder':"passphrase"}),
React.DOM.p({}, [
React.DOM.input({'key':'confirm', 'className':'confirmCheckbox', 'type':'checkbox', 'name':'confirm', 'ref':'confirm'}),
React.DOM.span({}, "I understand that all my data will be deleted and that this action is irreversible.")
React.DOM.div({'className': 'content'}, [
React.DOM.form({'key':'form', 'className':'deleteAccountForm', 'onChange': this.handleFormChange, 'onSubmit':this.handleDeleteAccount}, [
React.DOM.div({'key':'fields'},[
React.DOM.label({'key':'username-label', 'htmlFor' :'name'}, "username"),
React.DOM.input({'key':'username', 'className':this.state['username'], 'type':'text', 'name':'name', 'ref':'username', 'placeholder':"username", 'autoCapitalize':'none'}),
React.DOM.label({'key':'passphrase-label', 'autoFocus': 'true', 'htmlFor' :'passphrase'}, "passphrase"),
React.DOM.input({'key':'passphrase', 'className':this.state['passphrase'], 'type':'password', 'name':'passphrase', 'ref':'passphrase', 'placeholder':"passphrase"}),
React.DOM.p({}, [
React.DOM.input({'key':'confirm', 'className':'confirmCheckbox', 'type':'checkbox', 'name':'confirm', 'ref':'confirm'}),
React.DOM.span({}, "I understand that all my data will be deleted and that this action is irreversible.")
]),
]),
]),
React.DOM.button({'key':'button', 'type':'submit', 'disabled':!this.shouldEnableDeleteAccountButton(), 'className':'button'}, "Delete my account")
//~ React.DOM.div({ref: 'errorMessage', className: 'errorMessage', style: {visibility: errorVisibility} }, this.state.error)
React.DOM.button({'key':'button', 'type':'submit', 'disabled':!this.shouldEnableDeleteAccountButton(), 'className':'button'}, "Delete my account")
//~ React.DOM.div({ref: 'errorMessage', className: 'errorMessage', style: {visibility: errorVisibility} }, this.state.error)
])
])
]);
},

View File

@@ -36,7 +36,9 @@ Clipperz.PM.UI.Components.ExtraFeatures.DevicePINClass = React.createClass({
render: function () {
return React.DOM.div({className:'extraFeature devicePIN'}, [
React.DOM.h1({}, "Device PIN"),
React.DOM.h3({}, this.props['PIN'])
React.DOM.div({'className': 'content'}, [
React.DOM.h3({}, this.props['PIN'])
])
]);
},

View File

@@ -101,27 +101,29 @@ Clipperz.PM.UI.Components.ExtraFeatures.PassphraseClass = React.createClass({
render: function () {
return React.DOM.div({className:'extraFeature passphrase'}, [
React.DOM.h1({}, "Change Passphrase"),
React.DOM.form({'key':'form', 'className':'changePassphraseForm', 'onChange': this.handleFormChange, 'onSubmit':this.handleChangePassphrase}, [
React.DOM.div({'key':'fields'},[
React.DOM.label({'key':'username-label', 'htmlFor' :'name'}, "username"),
React.DOM.input({'key':'username', 'className':this.state['username'], 'type':'text', 'name':'name', 'ref':'username', 'placeholder':"username", 'autoCapitalize':'none'}),
React.DOM.div({'className': 'content'}, [
React.DOM.form({'key':'form', 'className':'changePassphraseForm', 'onChange': this.handleFormChange, 'onSubmit':this.handleChangePassphrase}, [
React.DOM.div({'key':'fields'},[
React.DOM.label({'key':'username-label', 'htmlFor' :'name'}, "username"),
React.DOM.input({'key':'username', 'className':this.state['username'], 'type':'text', 'name':'name', 'ref':'username', 'placeholder':"username", 'autoCapitalize':'none'}),
React.DOM.label({'key':'old-passphrase-label', 'htmlFor' :'old-passphrase'}, "old passphrase"),
React.DOM.input({'key':'old-passphrase', 'className':this.state['old-passphrase'], 'type':'password', 'name':'old-passphrase', 'ref':'old-passphrase', 'placeholder':"old passphrase"}),
React.DOM.label({'key':'old-passphrase-label', 'htmlFor' :'old-passphrase'}, "old passphrase"),
React.DOM.input({'key':'old-passphrase', 'className':this.state['old-passphrase'], 'type':'password', 'name':'old-passphrase', 'ref':'old-passphrase', 'placeholder':"old passphrase"}),
React.DOM.label({'key':'new-passphrase-label', 'autoFocus': 'true', 'htmlFor' :'new-passphrase'}, "new passphrase"),
React.DOM.input({'key':'new-passphrase', 'className':this.state['new-passphrase'], 'type':'password', 'name':'new-passphrase', 'ref':'new-passphrase', 'placeholder':"new passphrase"}),
React.DOM.label({'key':'new-passphrase-label', 'autoFocus': 'true', 'htmlFor' :'new-passphrase'}, "new passphrase"),
React.DOM.input({'key':'new-passphrase', 'className':this.state['new-passphrase'], 'type':'password', 'name':'new-passphrase', 'ref':'new-passphrase', 'placeholder':"new passphrase"}),
React.DOM.label({'key':'confirm-new-passphrase-label', 'htmlFor' :'confirm-new-passphrase'}, "confirm new passphrase"),
React.DOM.input({'key':'confirm-new-passphrase', 'className':this.state['confirm-new-passphrase'], 'type':'password', 'name':'confirm-new-passphrase', 'ref':'confirm-new-passphrase', 'placeholder':"confirm new passphrase"}),
React.DOM.label({'key':'confirm-new-passphrase-label', 'htmlFor' :'confirm-new-passphrase'}, "confirm new passphrase"),
React.DOM.input({'key':'confirm-new-passphrase', 'className':this.state['confirm-new-passphrase'], 'type':'password', 'name':'confirm-new-passphrase', 'ref':'confirm-new-passphrase', 'placeholder':"confirm new passphrase"}),
React.DOM.p({}, [
React.DOM.input({'key':'confirm', 'className':'confirmCheckbox', 'type':'checkbox', 'name':'confirm', 'ref':'confirm'}),
React.DOM.span({}, "I understand that Clipperz will not be able to recover a lost passphrase.")
React.DOM.p({}, [
React.DOM.input({'key':'confirm', 'className':'confirmCheckbox', 'type':'checkbox', 'name':'confirm', 'ref':'confirm'}),
React.DOM.span({}, "I understand that Clipperz will not be able to recover a lost passphrase.")
]),
]),
]),
React.DOM.button({'key':'button', 'type':'submit', 'disabled':!this.shouldEnableChangePassphraseButton(), 'className':'button'}, "Change passphrase"),
]),
React.DOM.button({'key':'button', 'type':'submit', 'disabled':!this.shouldEnableChangePassphraseButton(), 'className':'button'}, "Change passphrase"),
])
])
]);
},

View File

@@ -53,11 +53,15 @@ Clipperz.Base.extend(Clipperz.PM.UI.Components.Overlay, Object, {
//-------------------------------------------------------------------------
'show': function (aMessage, showMask) {
'show': function (aMessage, showMask, showProgress) {
if (showMask === true) {
this.showMask();
}
if (showProgress === true) {
this.showProgressBar();
}
this.resetStatus();
this.setMessage(aMessage);
MochiKit.DOM.removeElementClass(this.element(), 'ios-overlay-hide');
@@ -66,6 +70,7 @@ Clipperz.Base.extend(Clipperz.PM.UI.Components.Overlay, Object, {
'done': function (aMessage, aDelayBeforeHiding) {
this.hideMask();
this.hideProgressBar();
this.completed(this.showDoneIcon, aMessage, aDelayBeforeHiding);
},
@@ -114,6 +119,7 @@ Clipperz.Base.extend(Clipperz.PM.UI.Components.Overlay, Object, {
'hide': function () {
var element = this.element();
this.hideProgressBar();
MochiKit.DOM.removeElementClass(element, 'ios-overlay-show');
MochiKit.DOM.addElementClass(element, 'ios-overlay-hide');
return MochiKit.Async.callLater(1, MochiKit.Style.hideElement, element);
@@ -133,6 +139,21 @@ Clipperz.Base.extend(Clipperz.PM.UI.Components.Overlay, Object, {
//-------------------------------------------------------------------------
'showProgressBar': function () {
MochiKit.Style.showElement(this.getElement('progressBar'));
},
'hideProgressBar': function () {
MochiKit.Style.hideElement(this.getElement('progressBar'));
},
'updateProgress': function (aProgressPercentage) {
MochiKit.Style.setElementDimensions(this.getElement('progress'), {'w': aProgressPercentage}, '%');
//console.log("OVERLAY - updating progress: " + aProgressPercentage + "%");
},
//-------------------------------------------------------------------------
'defaultDelay': function () {
return this._defaultDelay;
},

View File

@@ -70,9 +70,13 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
//=========================================================================
showExtraFeatureComponent: function (aComponentName) {
toggleExtraFeatureComponent: function (aComponentName) {
return MochiKit.Base.bind(function () {
this.showExtraFeatureContent(Clipperz.PM.UI.Components.ExtraFeatures[aComponentName], aComponentName);
if (this.state['extraFeatureComponentName'] != aComponentName) {
this.showExtraFeatureContent(Clipperz.PM.UI.Components.ExtraFeatures[aComponentName], aComponentName);
} else {
this.hideExtraFeatureContent();
}
}, this);
},
@@ -118,7 +122,7 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
React.DOM.li({'key':'account', 'className':this.state['index']['account'] ? 'open' : 'closed'}, [
React.DOM.h1({'key':'accountH1', 'onClick':this.toggleIndexState('account')}, "Account"),
React.DOM.ul({'key':'accountUL'}, [
React.DOM.li({'key':'account_1', 'onClick':this.showExtraFeatureComponent('Passphrase'), 'className':(this.state['extraFeatureComponentName'] == 'Passphrase') ? 'selected' : ''}, [
React.DOM.li({'key':'account_1', 'onClick':this.toggleExtraFeatureComponent('Passphrase'), 'className':(this.state['extraFeatureComponentName'] == 'Passphrase') ? 'selected' : ''}, [
React.DOM.h2({'key':'account_1_h2'}, "Passphrase"),
React.DOM.div({'key':'account_1_div'}, [
React.DOM.p({'key':'account_1_p'}, "Change your account passphrase.")
@@ -130,7 +134,7 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
React.DOM.p({}, "")
])
]),
React.DOM.li({'key':'account_3', 'onClick':this.showExtraFeatureComponent('DevicePIN')}, [
React.DOM.li({'key':'account_3', 'onClick':this.toggleExtraFeatureComponent('DevicePIN')}, [
React.DOM.h2({}, "Device PIN"),
React.DOM.div({}, [
React.DOM.p({}, "Configure a PIN that will allow to get access to your cards, but only on this device.")
@@ -142,7 +146,7 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
React.DOM.p({}, "")
])
]),
React.DOM.li({'key':'account_5', 'onClick':this.showExtraFeatureComponent('DeleteAccount'), 'className':(this.state['extraFeatureComponentName'] == 'DeleteAccount') ? 'selected' : ''}, [
React.DOM.li({'key':'account_5', 'onClick':this.toggleExtraFeatureComponent('DeleteAccount'), 'className':(this.state['extraFeatureComponentName'] == 'DeleteAccount') ? 'selected' : ''}, [
React.DOM.h2({}, "Delete account"),
React.DOM.div({}, [
React.DOM.p({}, "Delete your account for good.")
@@ -182,29 +186,29 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
React.DOM.li({'key':'data', 'className':this.state['index']['data'] ? 'open' : 'closed'}, [
React.DOM.h1({'onClick':this.toggleIndexState('data')}, "Data"),
React.DOM.ul({'key':'data'}, [
React.DOM.li({'key':'data_1'}, [
React.DOM.h2({}, "Offline copy"),
React.DOM.div({}, [
React.DOM.p({}, "With just one click you can dump all your encrypted data from Clipperz servers to your hard disk and create a read-only offline version of Clipperz to be used when you are not connected to the Internet."),
React.DOM.a({'className':Clipperz.PM.UI.Components.classNames(offlineCopyButtonClasses), 'onClick':this.handleDownloadOfflineCopyLink}, "Download")
])
]),
// React.DOM.li({'key':'data_1', 'className':'offlineCopy'}, [
// React.DOM.h2({}, "Offline copy"),
// React.DOM.div({}, [
// React.DOM.p({}, "With just one click you can dump all your encrypted data from Clipperz servers to your hard disk and create a read-only offline version of Clipperz to be used when you are not connected to the Internet."),
// React.DOM.a({'className':Clipperz.PM.UI.Components.classNames(offlineCopyButtonClasses), 'onClick':this.handleDownloadOfflineCopyLink}, "Download")
// ])
// ]),
React.DOM.li({'key':'data_2'}, [
React.DOM.h2({}, "Import"),
React.DOM.div({}, [
React.DOM.p({}, "")
React.DOM.p({}, "CSV, JSON, …")
])
]),
React.DOM.li({'key':'data_3', 'onClick':this.showExtraFeatureComponent('DataExport')}, [
React.DOM.li({'key':'data_3', 'onClick':this.toggleExtraFeatureComponent('DataExport'), 'className':(this.state['extraFeatureComponentName'] == 'DataExport') ? 'selected' : ''}, [
React.DOM.h2({}, "Export"),
React.DOM.div({}, [
React.DOM.p({}, "")
React.DOM.p({}, "Offline copy, printable version, JSON, …")
])
]),
React.DOM.li({'key':'data_4'}, [
React.DOM.h2({}, "Sharing"),
React.DOM.div({}, [
React.DOM.p({}, "")
React.DOM.p({}, "Securely share cards with other users")
])
])
])
@@ -229,17 +233,20 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
render: function () {
//console.log("ExtraFeaturesPanel props", this.props);
var isOpen = (this.props['settingsPanelStatus'] == 'OPEN');
var isFullyOpen = isOpen && this.state['isFullyOpen'];
var classes = {
'panel': true,
'right': true,
'open': this.props['settingsPanelStatus'] == 'OPEN',
'fullOpen': this.state['isFullyOpen']
'open': isOpen,
'fullOpen': isFullyOpen
}
return React.DOM.div({'key':'extraFeaturesPanel', 'id':'extraFeaturesPanel', 'className':Clipperz.PM.UI.Components.classNames(classes)}, [
this.renderIndex(),
this.renderContent(),
// (this.props['settingsPanelStatus'] == 'OPEN') ? this.renderContent() : null,
]);
}