Merge pull request #70 from lbryio/modals

Use React Modal dialogs for all alerts and confirmation boxes
This commit is contained in:
alexliebowitz 2016-11-10 07:05:16 -05:00 committed by GitHub
commit 14565ec586
11 changed files with 371 additions and 103 deletions

6
dist/index.html vendored
View file

@ -20,8 +20,9 @@
</head>
<body>
<div id="canvas"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.6/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.6/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.2/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.2/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-modal/1.5.2/react-modal.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.7.4/polyfill.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Clamp.js/0.5.1/clamp.min.js"></script>
<script src="./js/mediaelement/jquery.js"></script>
@ -32,6 +33,7 @@
<script src="./js/component/form.js?i=0"></script>
<script src="./js/component/link.js?i=0"></script>
<script src="./js/component/menu.js?i=0"></script>
<script src="./js/component/modal.js?i=0"></script>
<script src="./js/component/header.js?i=0"></script>
<script src="./js/component/drawer.js?i=0"></script>
<script src="./js/component/splash.js?i=0"></script>

View file

@ -11,35 +11,37 @@ var App = React.createClass({
viewingPage: viewingPage,
drawerOpen: drawerOpenRaw !== null ? JSON.parse(drawerOpenRaw) : true,
pageArgs: val,
modal: null,
startNotice: null,
updateUrl: null,
isOldOSX: null,
};
},
componentDidMount: function() {
lbry.getStartNotice(function(notice) {
if (notice) {
alert(notice);
this.setState({
modal: 'startNotice',
startNotice: notice
});
}
});
},
componentWillMount: function() {
lbry.checkNewVersionAvailable(function(isAvailable) {
lbry.checkNewVersionAvailable((isAvailable) => {
if (!isAvailable || sessionStorage.getItem('upgradeSkipped')) {
return;
}
var message = 'The version of LBRY you\'re using is not up to date.\n\n' +
'Choose "OK" to download the latest version.';
lbry.getVersionInfo(function(versionInfo) {
lbry.getVersionInfo((versionInfo) => {
var isOldOSX = false;
if (versionInfo.os_system == 'Darwin') {
var updateUrl = 'https://lbry.io/get/lbry.dmg';
var maj, min, patch;
[maj, min, patch] = versionInfo.lbrynet_version.split('.');
if (maj == 0 && min <= 2 && patch <= 2) {
// On OS X with version <= 0.2.2, we need to notify user to close manually close LBRY
message += '\n\nBefore installing the new version, make sure to exit LBRY, if you started the app ' +
'click that LBRY icon in your status bar and choose "Quit."';
isOldOSX = true;
}
} else if (versionInfo.os_system == 'Linux') {
var updateUrl = 'https://lbry.io/get/lbry.deb';
@ -49,13 +51,11 @@ var App = React.createClass({
var updateUrl = 'https://lbry.io/get';
}
if (window.confirm(message))
{
lbry.stop();
window.location = updateUrl;
} else {
sessionStorage.setItem('upgradeSkipped', true);
};
this.setState({
modal: 'upgrade',
isOldOSX: isOldOSX,
updateUrl: updateUrl,
})
});
});
},
@ -67,6 +67,21 @@ var App = React.createClass({
sessionStorage.setItem('drawerOpen', false);
this.setState({ drawerOpen: false });
},
closeModal: function() {
this.setState({
modal: null,
});
},
handleUpgradeClicked: function() {
lbry.stop();
window.location = this.state.updateUrl;
},
handleSkipClicked: function() {
sessionStorage.setItem('upgradeSkipped', true);
this.setState({
modal: null,
});
},
onSearch: function(term) {
this.setState({
viewingPage: 'discover',
@ -151,6 +166,17 @@ var App = React.createClass({
<Header onOpenDrawer={this.openDrawer} onSearch={this.onSearch} links={headerLinks} viewingPage={this.state.viewingPage} />
{mainContent}
</div>
<Modal isOpen={this.state.modal == 'startNotice'} onConfirmed={this.closeModal}>
{this.state.startNotice}
</Modal>
<Modal isOpen={this.state.modal == 'upgrade'} type="confirm" confirmButtonLabel="Upgrade" abortButtonLabel="Skip"
onConfirmed={this.handleUpgradeClicked} onAborted={this.handleSkipClicked} >
<p>The version of LBRY you're using is not up to date. Choose "Upgrade" to get the latest version.</p>
{this.state.isOldOSX
? <p>Before installing the new version, make sure to exit LBRY. If you started the app, click the LBRY icon in your status bar and choose "Quit."</p>
: null}
</Modal>
</div>
);
}

View file

@ -93,13 +93,22 @@ var DownloadLink = React.createClass({
getInitialState: function() {
return {
downloading: false,
filePath: null,
modal: null,
}
},
closeModal: function() {
this.setState({
modal: null,
})
},
handleClick: function() {
lbry.getCostEstimate(this.props.streamName, (amount) => {
lbry.getBalance((balance) => {
if (amount > balance) {
alert("You don't have enough LBRY credits to pay for this stream.");
this.setState({
modal: 'notEnoughCredits',
});
} else {
this.startDownload();
}
@ -113,15 +122,27 @@ var DownloadLink = React.createClass({
});
lbry.getStream(this.props.streamName, (streamInfo) => {
alert('Downloading to ' + streamInfo.path);
console.log(streamInfo);
this.setState({
modal: 'downloadStarted',
filePath: streamInfo.path,
});
});
}
},
render: function() {
var label = (!this.state.downloading ? this.props.label : this.props.downloadingLabel);
return <Link button={this.props.button} hidden={this.props.hidden} style={this.props.style}
disabled={this.state.downloading} label={label} icon={this.props.icon} onClick={this.handleClick} />;
return (
<span className="button-container">
<Link button={this.props.button} hidden={this.props.hidden} style={this.props.style}
disabled={this.state.downloading} label={label} icon={this.props.icon} onClick={this.handleClick} />
<Modal isOpen={this.state.modal == 'downloadStarted'} onConfirmed={this.closeModal}>
Downloading to {this.state.filePath}
</Modal>
<Modal isOpen={this.state.modal == 'notEnoughCredits'} onConfirmed={this.closeModal}>
You don't have enough LBRY credits to pay for this stream.
</Modal>
</span>
);
}
});
@ -138,22 +159,40 @@ var WatchLink = React.createClass({
lbry.getCostEstimate(this.props.streamName, (amount) => {
lbry.getBalance((balance) => {
if (amount > balance) {
alert("You don't have enough LBRY credits to pay for this stream.");
this.setState({
modal: 'notEnoughCredits',
});
} else {
window.location = '?watch=' + this.props.streamName;
}
});
});
},
getInitialState: function() {
return {
modal: null,
};
},
closeModal: function() {
this.setState({
modal: null,
});
},
getDefaultProps: function() {
return {
icon: 'icon-play',
label: 'Watch',
}
},
render: function() {
return <Link button={this.props.button} hidden={this.props.hidden} style={this.props.style}
label={this.props.label} icon={this.props.icon} onClick={this.handleClick} />;
return (
<span className="button-container">
<Link button={this.props.button} hidden={this.props.hidden} style={this.props.style}
label={this.props.label} icon={this.props.icon} onClick={this.handleClick} />
<Modal isOpen={this.state.modal == 'notEnoughCredits'} onConfirmed={this.closeModal}>
You don't have enough LBRY credits to pay for this stream.
</Modal>
</span>
);
}
});

57
js/component/modal.js Normal file
View file

@ -0,0 +1,57 @@
var Modal = React.createClass({
propTypes: {
type: React.PropTypes.oneOf(['alert', 'confirm', 'custom']),
onConfirmed: React.PropTypes.func,
onAborted: React.PropTypes.func,
confirmButtonLabel: React.PropTypes.string,
abortButtonLabel: React.PropTypes.string,
confirmButtonDisabled: React.PropTypes.bool,
abortButtonDisabled: React.PropTypes.bool,
},
getDefaultProps: function() {
return {
type: 'alert',
confirmButtonLabel: 'OK',
abortButtonLabel: 'Cancel',
confirmButtonDisabled: false,
abortButtonDisabled: false,
};
},
render: function() {
var props = Object.assign({}, this.props);
if (typeof props.className == 'undefined') {
props.className = 'modal';
} else {
props.className += ' modal';
}
if (typeof props.overlayClassName == 'undefined') {
props.overlayClassName = 'modal-overlay';
} else {
props.overlayClassName += ' modal-overlay';
}
props.onCloseRequested = props.onAborted || props.onConfirmed;
if (this.props.type == 'custom') {
var buttons = null;
} else {
var buttons = (
<div className="modal__buttons">
{this.props.type == 'confirm'
? <Link button="alt" label={props.abortButtonLabel} className="modal__button" disabled={this.props.abortButtonDisabled} onClick={props.onAborted} />
: null}
<Link button="primary" label={props.confirmButtonLabel} className="modal__button" disabled={this.props.confirmButtonDisabled} onClick={props.onConfirmed} />
</div>
);
}
return (
<ReactModal {...props}>
{this.props.children}
{buttons}
</ReactModal>
);
}
});

View file

@ -14,6 +14,10 @@ var ClaimCodePage = React.createClass({
getInitialState: function() {
return {
submitting: false,
modal: null,
referralCredits: null,
activationCredits: null,
failureReason: null,
}
},
handleSubmit: function(event) {
@ -22,15 +26,19 @@ var ClaimCodePage = React.createClass({
}
if (!this.refs.code.value) {
alert('Please enter an invitation code or choose "Skip."');
this.setState({
modal: 'missingCode',
});
return;
} else if (!this.refs.email.value) {
alert('Please enter an email address or choose "Skip."');
this.setState({
modal: 'missingEmail',
});
return;
}
this.setState({
submitting: true
submitting: true,
});
lbry.getNewAddress((address) => {
@ -42,33 +50,25 @@ var ClaimCodePage = React.createClass({
var response = JSON.parse(xhr.responseText);
if (response.success) {
var redeemMessage = 'Your invite code has been redeemed. ';
if (response.referralCredits > 0) {
redeemMessage += 'You have also earned ' + response.referralCredits + ' credits from referrals. A total of ' +
(response.activationCredits + response.referralCredits) + ' will be added to your balance shortly.';
} else if(response.activationCredits > 0) {
redeemMessage += response.activationCredits + ' credits will be added to your balance shortly.';
} else {
redeemMessage += 'The credits will be added to your balance shortly.';
}
alert(redeemMessage);
localStorage.setItem('claimCodeDone', true);
window.location = '?home';
} else {
alert(response.reason);
this.setState({
submitting: false
modal: 'codeRedeemed',
referralCredits: response.referralCredits,
activationCredits: response.activationCredits,
});
} else {
this.setState({
submitting: false,
modal: 'codeRedeemFailed',
failureReason: response.reason,
});
}
});
xhr.addEventListener('error', () => {
this.setState({
submitting: false
submitting: false,
modal: 'couldNotConnect',
});
alert('LBRY couldn\'t connect to our servers to confirm your invitation code. Please check your ' +
'internet connection. If you continue to have problems, you can still browse LBRY and ' +
'visit the Settings page to redeem your code later.');
});
xhr.open('POST', 'https://invites.lbry.io', true);
@ -78,10 +78,19 @@ var ClaimCodePage = React.createClass({
});
},
handleSkip: function() {
alert('Welcome to LBRY! You can visit the Wallet page to redeem an invite code at any time.');
this.setState({
modal: 'skipped',
});
},
handleFinished: function() {
localStorage.setItem('claimCodeDone', true);
window.location = '?home';
},
closeModal: function() {
this.setState({
modal: null,
});
},
render: function() {
return (
<main>
@ -105,6 +114,31 @@ var ClaimCodePage = React.createClass({
</section>
</div>
</form>
<Modal isOpen={this.state.modal == 'missingCode'} onConfirmed={this.closeModal}>
Please enter an invitation code or choose "Skip."
</Modal>
<Modal isOpen={this.state.modal == 'missingEmail'} onConfirmed={this.closeModal}>
Please enter an email address or choose "Skip."
</Modal>
<Modal isOpen={this.state.modal == 'codeRedeemFailed'} onConfirmed={this.closeModal}>
{this.state.failureReason}
</Modal>
<Modal isOpen={this.state.modal == 'codeRedeemed'} onConfirmed={this.handleFinished}>
Your invite code has been redeemed.
{this.state.referralCredits > 0
? `You have also earned {referralCredits} credits from referrals. A total of {activationCredits + referralCredits}
will be added to your balance shortly.`
: (this.state.activationCredits > 0
? `{this.state.activationCredits} credits will be added to your balance shortly.`
: 'The credits will be added to your balance shortly.')}
</Modal>
<Modal isOpen={this.state.modal == 'skipped'} onConfirmed={this.handleFinished}>
Welcome to LBRY! You can visit the Wallet page to redeem an invite code at any time.
</Modal>
<Modal isOpen={this.state.modal == 'couldNotConnect'} onConfirmed={this.closeModal}>
<p>LBRY couldn't connect to our servers to confirm your invitation code. Please check your internet connection.</p>
If you continue to have problems, you can still browse LBRY and visit the Settings page to redeem your code later.
</Modal>
</main>
);
}

View file

@ -5,31 +5,48 @@ var moreMenuStyle = {
right: '13px',
};
var MyFilesRowMoreMenu = React.createClass({
onRevealClicked: function() {
propTypes: {
title: React.PropTypes.string.isRequired,
path: React.PropTypes.string.isRequired,
completed: React.PropTypes.bool.isRequired,
lbryUri: React.PropTypes.string.isRequired,
},
handleRevealClicked: function() {
lbry.revealFile(this.props.path);
},
onRemoveClicked: function() {
handleRemoveClicked: function() {
lbry.deleteFile(this.props.lbryUri, false);
},
onDeleteClicked: function() {
var alertText = 'Are you sure you\'d like to delete "' + this.props.title + '?" This will ' +
(this.completed ? ' stop the download and ' : '') +
'permanently remove the file from your system.';
if (confirm(alertText)) {
lbry.deleteFile(this.props.lbryUri);
}
handleDeleteClicked: function() {
this.setState({
modal: 'confirmDelete',
});
},
handleDeleteConfirmed: function() {
lbry.deleteFile(this.props.lbryUri);
lbry.setState({
modal: null,
});
},
getInitialState: function() {
return {
modal: null,
};
},
render: function() {
return (
<div style={moreMenuStyle}>
<Menu {...this.props}>
<section className="card">
<MenuItem onClick={this.onRevealClicked} label="Reveal file" /> {/* @TODO: Switch to OS specific wording */}
<MenuItem onClick={this.onRemoveClicked} label="Remove from LBRY" />
<MenuItem onClick={this.onDeleteClicked} label="Remove and delete file" />
<MenuItem onClick={this.handleRevealClicked} label="Reveal file" /> {/* @TODO: Switch to OS specific wording */}
<MenuItem onClick={this.handleRemoveClicked} label="Remove from LBRY" />
<MenuItem onClick={this.handleDeleteClicked} label="Remove and delete file" />
</section>
</Menu>
<Modal isOpen={this.state.modal == 'confirmDelete'} type="confirm" confirmButtonLabel="Delete File" onConfirmed={this.handleDeleteConfirmed}>
Are you sure you'd like to delete <cite>{this.props.title}</cite>? This will {this.props.completed ? ' stop the download and ' : ''}
permanently remove the file from your system.
</Modal>
</div>
);
}
@ -123,8 +140,8 @@ var MyFilesRow = React.createClass({
<div style={moreButtonContainerStyle}>
<Link style={moreButtonStyle} ref="moreButton" icon="icon-ellipsis-h" title="More Options" />
<MyFilesRowMoreMenu toggleButton={this.refs.moreButton} title={this.props.title}
lbryUri={this.props.lbryUri} fileName={this.props.fileName}
path={this.props.path}/>
completed={this.props.completed} lbryUri={this.props.lbryUri}
fileName={this.props.fileName} path={this.props.path}/>
</div>
}
</div>

View file

@ -81,14 +81,8 @@ var PublishPage = React.createClass({
console.log(publishArgs);
lbry.publish(publishArgs, (message) => {
this.handlePublishStarted();
this.setState({
submitting: false,
});
}, null, (error) => {
this.handlePublishError(error);
this.setState({
submitting: false,
});
});
};
@ -125,18 +119,26 @@ var PublishPage = React.createClass({
otherLicenseUrl: '',
uploadProgress: 0.0,
uploaded: false,
errorMessage: null,
tempFileReady: false,
submitting: false,
modal: null,
};
},
handlePublishStarted: function() {
alert(`Your file ${this.refs.meta_title.getValue()} has been published to LBRY at the address lbry://${this.state.name}!\n\n` +
`You will now be taken to your My Files page, where your newly published file will be listed. Your file will take a few minutes to appear for other LBRY users; until then it will be listed as "pending."`);
this.setState({
modal: 'publishStarted',
});
},
handlePublishStartedConfirmed: function() {
window.location = "?published";
},
handlePublishError: function(error) {
alert(`The following error occurred when attempting to publish your file:\n\n` +
error.message);
this.setState({
submitting: false,
modal: 'error',
errorMessage: error.message,
});
},
handleNameChange: function(event) {
var rawName = event.target.value;
@ -463,6 +465,14 @@ var PublishPage = React.createClass({
<input type='submit' className='hidden' />
</div>
</form>
<Modal isOpen={this.state.modal == 'publishStarted'} onConfirmed={this.handlePublishStartedConfirmed}>
<p>Your file has been published to LBRY at the address <code>lbry://{this.state.name}</code>!</p>
You will now be taken to your My Files page, where your newly published file will be listed. The file will take a few minutes to appear for other LBRY users; until then it will be listed as "pending."
</Modal>
<Modal isOpen={this.state.modal == 'error'} onConfirmed={this.closeModal}>
The following error occurred when attempting to publish your file: {this.state.errorMessage}
</Modal>
</main>
);
}

View file

@ -14,6 +14,9 @@ var ReferralPage = React.createClass({
getInitialState: function() {
return {
submitting: false,
modal: null,
referralCredits: null,
failureReason: null,
}
},
handleSubmit: function(event) {
@ -22,15 +25,17 @@ var ReferralPage = React.createClass({
}
if (!this.refs.code.value) {
alert('Please enter a referral code.');
return;
this.setState({
modal: 'missingCode',
});
} else if (!this.refs.email.value) {
alert('Please enter an email address.');
return;
this.setState({
modal: 'missingEmail',
});
}
this.setState({
submitting: true
submitting: true,
});
lbry.getNewAddress((address) => {
@ -42,29 +47,24 @@ var ReferralPage = React.createClass({
var response = JSON.parse(xhr.responseText);
if (response.success) {
if (response.referralCredits > 0) {
alert('You have earned ' + response.referralCredits + ' credits from referrals. ' +
'We will credit your account shortly. Thanks!');
} else {
alert('You have not earned any new referral credits since the last time you checked. ' +
'Please check back in a week or two.');
}
window.location = '?home';
} else {
alert(response.reason);
this.setState({
submitting: false
modal: 'referralInfo',
referralCredits: response.referralCredits,
});
} else {
this.setState({
submitting: false,
modal: 'lookupFailed',
failureReason: response.reason,
});
}
});
xhr.addEventListener('error', () => {
this.setState({
submitting: false
submitting: false,
modal: 'couldNotConnect',
});
alert('LBRY couldn\'t connect to our servers to confirm your referral code. Please check your ' +
'internet connection.');
});
xhr.open('POST', 'https://invites.lbry.io/check', true);
@ -73,6 +73,15 @@ var ReferralPage = React.createClass({
'&email=' + encodeURIComponent(email));
});
},
closeModal: function() {
this.setState({
modal: null,
});
},
handleFinished: function() {
localStorage.setItem('claimCodeDone', true);
window.location = '?home';
},
render: function() {
return (
<main>
@ -94,6 +103,17 @@ var ReferralPage = React.createClass({
</section>
</div>
</form>
<Modal isOpen={this.state.modal == 'referralInfo'} onConfirmed={this.handleFinished}>
{this.state.referralCredits > 0
? `You have earned {response.referralCredits} credits from referrals. We will credit your account shortly. Thanks!`
: 'You have not earned any new referral credits since the last time you checked. Please check back in a week or two.'}
</Modal>
<Modal isOpen={this.state.modal == 'lookupFailed'} onConfirmed={this.closeModal}>
{this.state.failureReason}
</Modal>
<Modal isOpen={this.state.modal == 'couldNotConnect'} onConfirmed={this.closeModal}>
LBRY couldn't connect to our servers to confirm your referral code. Please check your internet connection.
</Modal>
</main>
);
}

View file

@ -6,9 +6,9 @@ var ReportPage = React.createClass({
});
lbry.reportBug(this._messageArea.value, () => {
this.setState({
submitting: false
submitting: false,
modal: 'submitted',
});
alert("Your bug report has been submitted! Thank you for your feedback.");
});
this._messageArea.value = '';
}
@ -16,9 +16,15 @@ var ReportPage = React.createClass({
componentDidMount: function() {
document.title = "Report an Issue";
},
closeModal: function() {
this.setState({
modal: null,
})
},
getInitialState: function() {
return {
submitting: false,
modal: null,
}
},
render: function() {
@ -38,6 +44,9 @@ var ReportPage = React.createClass({
<h3>Developer?</h3>
You can also <Link href="https://github.com/lbryio/lbry/issues" label="submit an issue on GitHub"/>.
</section>
<Modal isOpen={this.state.modal == 'submitted'} onConfirmed={this.closeModal}>
Your bug report has been submitted! Thank you for your feedback.
</Modal>
</main>
);
}

View file

@ -17,6 +17,7 @@ var AddressSection = React.createClass({
getInitialState: function() {
return {
address: null,
modal: null,
}
},
componentWillMount: function() {
@ -58,7 +59,9 @@ var SendToAddressSection = React.createClass({
if ((this.state.balance - this.state.amount) < 1)
{
alert("Insufficient balance: after this transaction you would have less than 1 LBC in your wallet.")
this.setState({
modal: 'insufficientBalance',
});
return;
}
@ -85,6 +88,11 @@ var SendToAddressSection = React.createClass({
})
});
},
closeModal: function() {
this.setState({
modal: null,
});
},
getInitialState: function() {
return {
address: "",
@ -136,6 +144,9 @@ var SendToAddressSection = React.createClass({
: ''
}
</form>
<Modal isOpen={this.state.modal === 'insufficientBalance'} onConfirmed={this.closeModal}>
Insufficient balance: after this transaction you would have less than 1 LBC in your wallet.
</Modal>
</section>
);
}

View file

@ -132,6 +132,13 @@ input[type="text"], input[type="search"]
}
}
.button-container {
+ .button-container
{
margin-left: 12px;
}
}
.button-block
{
cursor: pointer;
@ -143,10 +150,6 @@ input[type="text"], input[type="search"]
text-align: center;
border-radius: 2px;
text-transform: uppercase;
+ .button-block
{
margin-left: 12px;
}
.icon
{
top: 0em;
@ -225,3 +228,43 @@ input[type="text"], input[type="search"]
margin-top: $spacing-vertical;
}
}
.modal-overlay {
position: fixed;
display: flex;
justify-content: center;
align-items: center;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
background-color: rgba(255, 255, 255, 0.74902);
z-index: 9999;
}
.modal {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border: 1px solid rgb(204, 204, 204);
background: rgb(255, 255, 255);
overflow: auto;
border-radius: 4px;
outline: none;
padding: 40px;
max-width: 250px;
}
.modal__buttons {
display: flex;
flex-direction: row;
justify-content: center;
margin-top: 15px;
}
.modal__button {
margin: 0px 6px;
}