mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-23 07:38:07 -05:00
Merge pull request #2433 from paulkaplan/cloud-data-modal
Show username and cloud var alerts on projects that use them.
This commit is contained in:
commit
a99e8574f5
5 changed files with 124 additions and 68 deletions
36
src/lib/project-info.js
Normal file
36
src/lib/project-info.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
const EXTENSION_INFO = require('./extensions.js').default;
|
||||
|
||||
module.exports = {
|
||||
// Keys match the projectVersion key from project serialization
|
||||
3: {
|
||||
extensions: project => {
|
||||
(project.extensions || []).map(ext => EXTENSION_INFO[ext])
|
||||
.filter(ext => !!ext); // Only include extensions in the info lib
|
||||
},
|
||||
spriteCount: project =>
|
||||
project.targets.length - 1, // don't count stage
|
||||
scriptCount: project => project.targets
|
||||
.map(target => Object.values(target.blocks))
|
||||
.reduce((accumulator, currentVal) => accumulator.concat(currentVal), [])
|
||||
.filter(block => block.topLevel).length,
|
||||
usernameBlock: project => project.targets
|
||||
.map(target => Object.values(target.blocks))
|
||||
.reduce((accumulator, currentVal) => accumulator.concat(currentVal), [])
|
||||
.some(block => block.opcode === 'sensing_username'),
|
||||
cloudData: project => {
|
||||
const stage = project.targets[0];
|
||||
return Object.values(stage.variables)
|
||||
.some(variable => variable.length === 3); // 3 entries if cloud var
|
||||
}
|
||||
},
|
||||
2: {
|
||||
extensions: () => [], // Showing extension chip not implemented for scratch2 projects
|
||||
spriteCount: project => project.info.spriteCount,
|
||||
scriptCount: project => project.info.scriptCount,
|
||||
usernameBlock: project =>
|
||||
// Block traversing is complicated in scratch2 projects...
|
||||
// This check should work even if you have sprites named getUserName, etc.
|
||||
JSON.stringify(project).indexOf('["getUserName"]') !== -1,
|
||||
cloudData: project => project.info.hasCloudData
|
||||
}
|
||||
};
|
|
@ -32,5 +32,7 @@
|
|||
"project.numSprites": "{number} sprites",
|
||||
"project.descriptionMaxLength": "Description is too long",
|
||||
"project.notesPlaceholder": "How did you make this project? Did you use ideas, scripts or artwork from other people? Thank them here.",
|
||||
"project.descriptionPlaceholder": "Tell people how to use your project (such as which keys to press)."
|
||||
"project.descriptionPlaceholder": "Tell people how to use your project (such as which keys to press).",
|
||||
"project.cloudDataAlert": "This project uses cloud data - a feature that is only available to signed in users.",
|
||||
"project.usernameBlockAlert": "This project can detect who is using it, through the \"username\" block. To hide your identity, sign out before using the project."
|
||||
}
|
||||
|
|
|
@ -105,6 +105,8 @@ const PreviewPresentation = ({
|
|||
onUpdateProjectId,
|
||||
originalInfo,
|
||||
parentInfo,
|
||||
showCloudDataAlert,
|
||||
showUsernameBlockAlert,
|
||||
projectHost,
|
||||
projectId,
|
||||
projectInfo,
|
||||
|
@ -267,6 +269,16 @@ const PreviewPresentation = ({
|
|||
</FlexRow>
|
||||
<FlexRow className="preview-row">
|
||||
<div className="guiPlayer">
|
||||
{showCloudDataAlert && (
|
||||
<FlexRow className="project-info-alert">
|
||||
<FormattedMessage id="project.cloudDataAlert" />
|
||||
</FlexRow>
|
||||
)}
|
||||
{showUsernameBlockAlert && (
|
||||
<FlexRow className="project-info-alert">
|
||||
<FormattedMessage id="project.usernameBlockAlert" />
|
||||
</FlexRow>
|
||||
)}
|
||||
<IntlGUI
|
||||
isPlayerOnly
|
||||
assetHost={assetHost}
|
||||
|
@ -647,7 +659,9 @@ PreviewPresentation.propTypes = {
|
|||
replies: PropTypes.objectOf(PropTypes.array),
|
||||
reportOpen: PropTypes.bool,
|
||||
showAdminPanel: PropTypes.bool,
|
||||
showCloudDataAlert: PropTypes.bool,
|
||||
showModInfo: PropTypes.bool,
|
||||
showUsernameBlockAlert: PropTypes.bool,
|
||||
singleCommentId: PropTypes.oneOfType([PropTypes.number, PropTypes.bool]),
|
||||
visibilityInfo: PropTypes.shape({
|
||||
censored: PropTypes.bool,
|
||||
|
|
|
@ -347,8 +347,21 @@ $stage-width: 480px;
|
|||
|
||||
.guiPlayer {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: $player-width;
|
||||
|
||||
$alert-bg: rgba(255, 255, 255, .85);
|
||||
.project-info-alert {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
margin: 60px 15px;
|
||||
border-radius: .25rem;
|
||||
background: $alert-bg;
|
||||
padding: .75rem;
|
||||
text-align: center;
|
||||
font-size: .95rem;
|
||||
}
|
||||
|
||||
@media #{$small} {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
@ -13,10 +13,9 @@ const copy = require('clipboard-copy');
|
|||
const Page = require('../../components/page/www/page.jsx');
|
||||
const storage = require('../../lib/storage.js').default;
|
||||
const log = require('../../lib/log');
|
||||
const EXTENSION_INFO = require('../../lib/extensions.js').default;
|
||||
const jar = require('../../lib/jar.js');
|
||||
const thumbnailUrl = require('../../lib/user-thumbnail');
|
||||
|
||||
const ProjectInfo = require('../../lib/project-info');
|
||||
const PreviewPresentation = require('./presentation.jsx');
|
||||
const projectShape = require('./projectshape.jsx').projectShape;
|
||||
const Registration = require('../../components/registration/registration.jsx');
|
||||
|
@ -102,6 +101,8 @@ class Preview extends React.Component {
|
|||
scriptCount: 0,
|
||||
spriteCount: 0
|
||||
},
|
||||
showCloudDataAlert: false,
|
||||
showUsernameBlockAlert: false,
|
||||
projectId: parts[1] === 'editor' ? '0' : parts[1],
|
||||
reportOpen: false,
|
||||
singleCommentId: singleCommentId
|
||||
|
@ -118,7 +119,7 @@ class Preview extends React.Component {
|
|||
this.props.sessionStatus === sessionActions.Status.FETCHED) ||
|
||||
(this.state.projectId !== prevState.projectId))) {
|
||||
this.fetchCommunityData();
|
||||
this.getProjectData(this.state.projectId);
|
||||
this.getProjectData(this.state.projectId, true /* Show cloud/username alerts */);
|
||||
if (this.state.justShared) {
|
||||
this.setState({ // eslint-disable-line react/no-did-update-set-state
|
||||
justShared: false
|
||||
|
@ -155,7 +156,10 @@ class Preview extends React.Component {
|
|||
|
||||
// Switching out of editor mode, refresh data that comes from project json
|
||||
if (this.props.playerMode && !prevProps.playerMode) {
|
||||
this.getProjectData(this.state.projectId);
|
||||
this.getProjectData(
|
||||
this.state.projectId,
|
||||
false // Do not show cloud/username alerts again
|
||||
);
|
||||
}
|
||||
}
|
||||
componentWillUnmount () {
|
||||
|
@ -215,71 +219,48 @@ class Preview extends React.Component {
|
|||
}
|
||||
}
|
||||
}
|
||||
getProjectData (projectId) {
|
||||
if (projectId > 0) {
|
||||
storage
|
||||
.load(storage.AssetType.Project, projectId, storage.DataFormat.JSON)
|
||||
.then(projectAsset => { // NOTE: this is turning up null, breaking the line below.
|
||||
let input = projectAsset.data;
|
||||
if (typeof input === 'object' && !(input instanceof ArrayBuffer) &&
|
||||
!ArrayBuffer.isView(input)) { // taken from scratch-vm
|
||||
// If the input is an object and not any ArrayBuffer
|
||||
// or an ArrayBuffer view (this includes all typed arrays and DataViews)
|
||||
// turn the object into a JSON string, because we suspect
|
||||
// this is a project.json as an object
|
||||
// validate expects a string or buffer as input
|
||||
// TODO not sure if we need to check that it also isn't a data view
|
||||
input = JSON.stringify(input);
|
||||
}
|
||||
parser(projectAsset.data, false, (err, projectData) => {
|
||||
if (err) {
|
||||
log.error(`Unhandled project parsing error: ${err}`);
|
||||
return;
|
||||
}
|
||||
const extensionSet = new Set();
|
||||
if (projectData[0].extensions) {
|
||||
projectData[0].extensions.forEach(extension => {
|
||||
const extensionInfo = EXTENSION_INFO[extension];
|
||||
if (extensionInfo) {
|
||||
extensionSet.add(extensionInfo);
|
||||
}
|
||||
});
|
||||
}
|
||||
let spriteCount = 0;
|
||||
let scriptCount = 0;
|
||||
if (projectData[0].projectVersion) {
|
||||
if (projectData[0].projectVersion === 3) {
|
||||
spriteCount = projectData[0].targets.length - 1; // don't count stage
|
||||
scriptCount = projectData[0].targets
|
||||
.map(target =>
|
||||
Object.values(target.blocks)
|
||||
.filter(block => block.topLevel).length
|
||||
)
|
||||
.reduce((accumulator, currentVal) => accumulator + currentVal, 0);
|
||||
} else if (projectData[0].projectVersion === 2) { // sb2 file
|
||||
spriteCount = projectData[0].info.spriteCount;
|
||||
scriptCount = projectData[0].info.scriptCount;
|
||||
}
|
||||
} // else sb (scratch 1.x) numbers will be zero
|
||||
|
||||
this.setState({
|
||||
extensions: Array.from(extensionSet),
|
||||
modInfo: {
|
||||
scriptCount: scriptCount,
|
||||
spriteCount: spriteCount
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
} else { // projectId is default or invalid; empty the extensions array
|
||||
this.setState({
|
||||
extensions: [],
|
||||
modInfo: {
|
||||
scriptCount: 0,
|
||||
spriteCount: 0
|
||||
getProjectData (projectId, showAlerts) {
|
||||
if (projectId <= 0) return 0;
|
||||
storage
|
||||
.load(storage.AssetType.Project, projectId, storage.DataFormat.JSON)
|
||||
.then(projectAsset => { // NOTE: this is turning up null, breaking the line below.
|
||||
let input = projectAsset.data;
|
||||
if (typeof input === 'object' && !(input instanceof ArrayBuffer) &&
|
||||
!ArrayBuffer.isView(input)) { // taken from scratch-vm
|
||||
// If the input is an object and not any ArrayBuffer
|
||||
// or an ArrayBuffer view (this includes all typed arrays and DataViews)
|
||||
// turn the object into a JSON string, because we suspect
|
||||
// this is a project.json as an object
|
||||
// validate expects a string or buffer as input
|
||||
// TODO not sure if we need to check that it also isn't a data view
|
||||
input = JSON.stringify(input);
|
||||
}
|
||||
parser(projectAsset.data, false, (err, projectData) => {
|
||||
if (err) {
|
||||
log.error(`Unhandled project parsing error: ${err}`);
|
||||
return;
|
||||
}
|
||||
const newState = {
|
||||
modInfo: {} // Filled in below
|
||||
};
|
||||
|
||||
const helpers = ProjectInfo[projectData[0].projectVersion];
|
||||
if (!helpers) return; // sb1 not handled
|
||||
newState.extensions = helpers.extensions(projectData[0]);
|
||||
newState.modInfo.scriptCount = helpers.scriptCount(projectData[0]);
|
||||
newState.modInfo.spriteCount = helpers.spriteCount(projectData[0]);
|
||||
|
||||
if (showAlerts) {
|
||||
// Check for username block only if user is logged in
|
||||
if (this.props.isLoggedIn) {
|
||||
newState.showUsernameBlockAlert = helpers.usernameBlock(projectData[0]);
|
||||
} else { // Check for cloud vars only if user is logged out
|
||||
newState.showCloudDataAlert = helpers.cloudData(projectData[0]);
|
||||
}
|
||||
}
|
||||
this.setState(newState);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
handleToggleComments () {
|
||||
this.props.updateProject(
|
||||
|
@ -354,6 +335,10 @@ class Preview extends React.Component {
|
|||
this.props.reportProject(this.state.projectId, formData, this.props.user.token);
|
||||
}
|
||||
handleGreenFlag () {
|
||||
this.setState({
|
||||
showUsernameBlockAlert: false,
|
||||
showCloudDataAlert: false
|
||||
});
|
||||
this.props.logProjectView(this.props.projectInfo.id, this.props.authorUsername, this.props.user.token);
|
||||
}
|
||||
handlePopState () {
|
||||
|
@ -446,6 +431,10 @@ class Preview extends React.Component {
|
|||
this.props.remixProject();
|
||||
}
|
||||
handleSeeInside () {
|
||||
this.setState({ // Remove any project alerts so they don't show up later
|
||||
showUsernameBlockAlert: false,
|
||||
showCloudDataAlert: false
|
||||
});
|
||||
this.props.setPlayer(false);
|
||||
if (this.state.justRemixed || this.state.justShared) {
|
||||
this.setState({
|
||||
|
@ -601,7 +590,9 @@ class Preview extends React.Component {
|
|||
replies={this.props.replies}
|
||||
reportOpen={this.state.reportOpen}
|
||||
showAdminPanel={this.props.isAdmin}
|
||||
showCloudDataAlert={this.state.showCloudDataAlert}
|
||||
showModInfo={this.props.isAdmin}
|
||||
showUsernameBlockAlert={this.state.showUsernameBlockAlert}
|
||||
singleCommentId={this.state.singleCommentId}
|
||||
visibilityInfo={this.props.visibilityInfo}
|
||||
onAddComment={this.handleAddComment}
|
||||
|
|
Loading…
Reference in a new issue