Spaces:
Running
Running
import classNames from 'classnames'; | |
import { connect } from 'react-redux'; | |
import { FormattedMessage, injectIntl } from 'react-intl'; | |
import PropTypes from 'prop-types'; | |
import React from 'react'; | |
import bindAll from 'lodash.bindall'; | |
import Button from '../button/button.jsx'; | |
import loadingIcon from './share-loading.svg'; | |
import styles from './share-button.css'; | |
const getProjectThumbnail = () => new Promise(resolve => { | |
window.vm.renderer.requestSnapshot(uri => { | |
resolve(uri); | |
}); | |
}); | |
const getProjectUri = () => new Promise(resolve => { | |
window.vm.saveProjectSb3().then(blob => new Promise(resolve => { | |
const reader = new FileReader(); | |
reader.onload = element => { | |
resolve(element.target.result); | |
}; | |
reader.readAsDataURL(blob); | |
})) | |
.then(resolve); | |
}); | |
const isUploadAvailable = async () => { | |
let res = null; | |
try { | |
res = await fetch('https://projects.penguinmod.com/api/v1/projects/canuploadprojects').then(res => res.json()); | |
} catch { | |
// failed to fetch entirely | |
return false; | |
} | |
return res.canUpload; | |
}; | |
class ShareButton extends React.Component { | |
constructor(props) { | |
super(props); | |
bindAll(this, [ | |
'handleMessageEvent', | |
'wrapperEventHandler', | |
'onUploadProject' | |
]); | |
this.state = { | |
loading: false, | |
imageUri: '' | |
}; | |
} | |
componentDidMount() { | |
window.addEventListener('message', this.wrapperEventHandler); | |
} | |
componentWillUnmount() { | |
window.removeEventListener('message', this.wrapperEventHandler); | |
} | |
wrapperEventHandler(e) { | |
this.handleMessageEvent(e); | |
} | |
async handleMessageEvent(e) { | |
if (!e.origin.startsWith(`https://penguinmod.com`)) { | |
return; | |
} | |
if (!e.data.p4) { | |
return; | |
} | |
const packagerData = e.data.p4; | |
if (packagerData.type !== 'validate') { | |
return; | |
} | |
const imageUri = this.state.imageUri; | |
e.source.postMessage({ | |
p4: { | |
type: 'image', | |
uri: imageUri | |
} | |
}, e.origin); | |
const projectUri = await getProjectUri(); | |
e.source.postMessage({ | |
p4: { | |
type: 'project', | |
uri: projectUri | |
} | |
}, e.origin); | |
e.source.postMessage({ | |
p4: { | |
type: 'finished' | |
} | |
}, e.origin); | |
} | |
async onUploadProject() { | |
if (this.state.loading) return; | |
if (!window.vm) return; | |
if (!window.vm.runtime) return; | |
if (!window.vm.renderer) return; | |
// get the project thumbnail | |
await new Promise((resolve) => { | |
getProjectThumbnail().then(dataUrl => { | |
this.setState({ | |
imageUri: dataUrl | |
}); | |
resolve(); | |
}); | |
window.vm.renderer.draw(); // force the callback to run | |
setTimeout(() => { | |
window.vm.renderer.draw(); // force the callback to run | |
}, 50); | |
setTimeout(() => { | |
window.vm.renderer.draw(); // force the callback to run | |
}, 100); | |
}); | |
this.setState({ | |
loading: true | |
}); | |
isUploadAvailable().then(available => { | |
this.setState({ | |
loading: false | |
}); | |
if (!available) { | |
// error? | |
console.warn('Project Server did not respond. Uploading is not available.'); | |
alert('Uploading is currently unavailable. Please wait for the server to be restored.'); | |
return; | |
} | |
const isEdit = this.props.usernameLoggedIn | |
&& this.props.extraProjectInfo?.author === this.props.username; | |
let editPiece = ''; | |
let remixPiece = ''; | |
const id = location.hash.replace('#', ''); | |
if (this.props.extraProjectInfo?.isRemix) { | |
remixPiece = `&remix=${id}`; | |
} | |
let targetPage = 'upload'; | |
if (isEdit) { | |
targetPage = 'edit'; | |
editPiece = `&id=${id}`; | |
} | |
const url = location.origin; | |
window.open(`https://penguinmod.com/${targetPage}?name=${this.props.projectTitle}${editPiece}${remixPiece}&external=${url}`, '_blank'); | |
}); | |
} | |
render() { | |
const isRemix = this.props.extraProjectInfo?.isRemix; | |
const isEdit = this.props.usernameLoggedIn | |
&& this.props.extraProjectInfo?.author === this.props.username; | |
return ( | |
<Button | |
className={classNames( | |
this.props.className, | |
styles.shareButton, | |
{ [styles.shareButtonIsShared]: this.props.isShared }, | |
{ [styles.disabled]: this.state.loading }, | |
)} | |
onClick={this.onUploadProject} | |
> | |
<div className={classNames(styles.shareContent)}> | |
{isEdit ? <FormattedMessage | |
defaultMessage="Upload Edits" | |
description="Text for uploading edits for projects on PenguinMod" | |
id="gui.menuBar.pmedit" | |
/> : | |
(isRemix ? | |
<FormattedMessage | |
defaultMessage="Remix" | |
description="Menu bar item for remixing" | |
id="gui.menuBar.remix" | |
/> : | |
<FormattedMessage | |
defaultMessage="Upload" | |
description="Label for project share button" | |
id="gui.menuBar.pmshare" | |
/>)} | |
{this.state.loading ? ( | |
<img | |
className={classNames(styles.icon)} | |
draggable={false} | |
src={loadingIcon} | |
height={20} | |
width={20} | |
/> | |
) : null} | |
</div> | |
</Button> | |
); | |
} | |
} | |
ShareButton.propTypes = { | |
className: PropTypes.string, | |
isShared: PropTypes.bool, | |
projectTitle: PropTypes.string, | |
extraProjectInfo: PropTypes.shape({ | |
accepted: PropTypes.bool, | |
isRemix: PropTypes.bool, | |
remixId: PropTypes.number, | |
tooLarge: PropTypes.bool, | |
author: PropTypes.string, | |
releaseDate: PropTypes.shape(Date), | |
isUpdated: PropTypes.bool | |
}), | |
username: PropTypes.string, | |
usernameLoggedIn: PropTypes.bool | |
}; | |
const mapStateToProps = state => ({ | |
projectTitle: state.scratchGui.projectTitle, | |
extraProjectInfo: state.scratchGui.tw.extraProjectInfo, | |
username: state.scratchGui.tw.username, | |
usernameLoggedIn: state.scratchGui.tw.usernameLoggedIn | |
}); | |
// eslint-disable-next-line no-unused-vars | |
const mapDispatchToProps = dispatch => ({}); | |
export default injectIntl(connect( | |
mapStateToProps, | |
mapDispatchToProps | |
)(ShareButton)); | |