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.numSprites": "{number} sprites",
|
||||||
"project.descriptionMaxLength": "Description is too long",
|
"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.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,
|
onUpdateProjectId,
|
||||||
originalInfo,
|
originalInfo,
|
||||||
parentInfo,
|
parentInfo,
|
||||||
|
showCloudDataAlert,
|
||||||
|
showUsernameBlockAlert,
|
||||||
projectHost,
|
projectHost,
|
||||||
projectId,
|
projectId,
|
||||||
projectInfo,
|
projectInfo,
|
||||||
|
@ -267,6 +269,16 @@ const PreviewPresentation = ({
|
||||||
</FlexRow>
|
</FlexRow>
|
||||||
<FlexRow className="preview-row">
|
<FlexRow className="preview-row">
|
||||||
<div className="guiPlayer">
|
<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
|
<IntlGUI
|
||||||
isPlayerOnly
|
isPlayerOnly
|
||||||
assetHost={assetHost}
|
assetHost={assetHost}
|
||||||
|
@ -647,7 +659,9 @@ PreviewPresentation.propTypes = {
|
||||||
replies: PropTypes.objectOf(PropTypes.array),
|
replies: PropTypes.objectOf(PropTypes.array),
|
||||||
reportOpen: PropTypes.bool,
|
reportOpen: PropTypes.bool,
|
||||||
showAdminPanel: PropTypes.bool,
|
showAdminPanel: PropTypes.bool,
|
||||||
|
showCloudDataAlert: PropTypes.bool,
|
||||||
showModInfo: PropTypes.bool,
|
showModInfo: PropTypes.bool,
|
||||||
|
showUsernameBlockAlert: PropTypes.bool,
|
||||||
singleCommentId: PropTypes.oneOfType([PropTypes.number, PropTypes.bool]),
|
singleCommentId: PropTypes.oneOfType([PropTypes.number, PropTypes.bool]),
|
||||||
visibilityInfo: PropTypes.shape({
|
visibilityInfo: PropTypes.shape({
|
||||||
censored: PropTypes.bool,
|
censored: PropTypes.bool,
|
||||||
|
|
|
@ -347,8 +347,21 @@ $stage-width: 480px;
|
||||||
|
|
||||||
.guiPlayer {
|
.guiPlayer {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
width: $player-width;
|
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} {
|
@media #{$small} {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,10 +13,9 @@ const copy = require('clipboard-copy');
|
||||||
const Page = require('../../components/page/www/page.jsx');
|
const Page = require('../../components/page/www/page.jsx');
|
||||||
const storage = require('../../lib/storage.js').default;
|
const storage = require('../../lib/storage.js').default;
|
||||||
const log = require('../../lib/log');
|
const log = require('../../lib/log');
|
||||||
const EXTENSION_INFO = require('../../lib/extensions.js').default;
|
|
||||||
const jar = require('../../lib/jar.js');
|
const jar = require('../../lib/jar.js');
|
||||||
const thumbnailUrl = require('../../lib/user-thumbnail');
|
const thumbnailUrl = require('../../lib/user-thumbnail');
|
||||||
|
const ProjectInfo = require('../../lib/project-info');
|
||||||
const PreviewPresentation = require('./presentation.jsx');
|
const PreviewPresentation = require('./presentation.jsx');
|
||||||
const projectShape = require('./projectshape.jsx').projectShape;
|
const projectShape = require('./projectshape.jsx').projectShape;
|
||||||
const Registration = require('../../components/registration/registration.jsx');
|
const Registration = require('../../components/registration/registration.jsx');
|
||||||
|
@ -102,6 +101,8 @@ class Preview extends React.Component {
|
||||||
scriptCount: 0,
|
scriptCount: 0,
|
||||||
spriteCount: 0
|
spriteCount: 0
|
||||||
},
|
},
|
||||||
|
showCloudDataAlert: false,
|
||||||
|
showUsernameBlockAlert: false,
|
||||||
projectId: parts[1] === 'editor' ? '0' : parts[1],
|
projectId: parts[1] === 'editor' ? '0' : parts[1],
|
||||||
reportOpen: false,
|
reportOpen: false,
|
||||||
singleCommentId: singleCommentId
|
singleCommentId: singleCommentId
|
||||||
|
@ -118,7 +119,7 @@ class Preview extends React.Component {
|
||||||
this.props.sessionStatus === sessionActions.Status.FETCHED) ||
|
this.props.sessionStatus === sessionActions.Status.FETCHED) ||
|
||||||
(this.state.projectId !== prevState.projectId))) {
|
(this.state.projectId !== prevState.projectId))) {
|
||||||
this.fetchCommunityData();
|
this.fetchCommunityData();
|
||||||
this.getProjectData(this.state.projectId);
|
this.getProjectData(this.state.projectId, true /* Show cloud/username alerts */);
|
||||||
if (this.state.justShared) {
|
if (this.state.justShared) {
|
||||||
this.setState({ // eslint-disable-line react/no-did-update-set-state
|
this.setState({ // eslint-disable-line react/no-did-update-set-state
|
||||||
justShared: false
|
justShared: false
|
||||||
|
@ -155,7 +156,10 @@ class Preview extends React.Component {
|
||||||
|
|
||||||
// Switching out of editor mode, refresh data that comes from project json
|
// Switching out of editor mode, refresh data that comes from project json
|
||||||
if (this.props.playerMode && !prevProps.playerMode) {
|
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 () {
|
componentWillUnmount () {
|
||||||
|
@ -215,71 +219,48 @@ class Preview extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getProjectData (projectId) {
|
getProjectData (projectId, showAlerts) {
|
||||||
if (projectId > 0) {
|
if (projectId <= 0) return 0;
|
||||||
storage
|
storage
|
||||||
.load(storage.AssetType.Project, projectId, storage.DataFormat.JSON)
|
.load(storage.AssetType.Project, projectId, storage.DataFormat.JSON)
|
||||||
.then(projectAsset => { // NOTE: this is turning up null, breaking the line below.
|
.then(projectAsset => { // NOTE: this is turning up null, breaking the line below.
|
||||||
let input = projectAsset.data;
|
let input = projectAsset.data;
|
||||||
if (typeof input === 'object' && !(input instanceof ArrayBuffer) &&
|
if (typeof input === 'object' && !(input instanceof ArrayBuffer) &&
|
||||||
!ArrayBuffer.isView(input)) { // taken from scratch-vm
|
!ArrayBuffer.isView(input)) { // taken from scratch-vm
|
||||||
// If the input is an object and not any ArrayBuffer
|
// If the input is an object and not any ArrayBuffer
|
||||||
// or an ArrayBuffer view (this includes all typed arrays and DataViews)
|
// or an ArrayBuffer view (this includes all typed arrays and DataViews)
|
||||||
// turn the object into a JSON string, because we suspect
|
// turn the object into a JSON string, because we suspect
|
||||||
// this is a project.json as an object
|
// this is a project.json as an object
|
||||||
// validate expects a string or buffer as input
|
// validate expects a string or buffer as input
|
||||||
// TODO not sure if we need to check that it also isn't a data view
|
// TODO not sure if we need to check that it also isn't a data view
|
||||||
input = JSON.stringify(input);
|
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
|
|
||||||
}
|
}
|
||||||
|
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 () {
|
handleToggleComments () {
|
||||||
this.props.updateProject(
|
this.props.updateProject(
|
||||||
|
@ -354,6 +335,10 @@ class Preview extends React.Component {
|
||||||
this.props.reportProject(this.state.projectId, formData, this.props.user.token);
|
this.props.reportProject(this.state.projectId, formData, this.props.user.token);
|
||||||
}
|
}
|
||||||
handleGreenFlag () {
|
handleGreenFlag () {
|
||||||
|
this.setState({
|
||||||
|
showUsernameBlockAlert: false,
|
||||||
|
showCloudDataAlert: false
|
||||||
|
});
|
||||||
this.props.logProjectView(this.props.projectInfo.id, this.props.authorUsername, this.props.user.token);
|
this.props.logProjectView(this.props.projectInfo.id, this.props.authorUsername, this.props.user.token);
|
||||||
}
|
}
|
||||||
handlePopState () {
|
handlePopState () {
|
||||||
|
@ -446,6 +431,10 @@ class Preview extends React.Component {
|
||||||
this.props.remixProject();
|
this.props.remixProject();
|
||||||
}
|
}
|
||||||
handleSeeInside () {
|
handleSeeInside () {
|
||||||
|
this.setState({ // Remove any project alerts so they don't show up later
|
||||||
|
showUsernameBlockAlert: false,
|
||||||
|
showCloudDataAlert: false
|
||||||
|
});
|
||||||
this.props.setPlayer(false);
|
this.props.setPlayer(false);
|
||||||
if (this.state.justRemixed || this.state.justShared) {
|
if (this.state.justRemixed || this.state.justShared) {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -601,7 +590,9 @@ class Preview extends React.Component {
|
||||||
replies={this.props.replies}
|
replies={this.props.replies}
|
||||||
reportOpen={this.state.reportOpen}
|
reportOpen={this.state.reportOpen}
|
||||||
showAdminPanel={this.props.isAdmin}
|
showAdminPanel={this.props.isAdmin}
|
||||||
|
showCloudDataAlert={this.state.showCloudDataAlert}
|
||||||
showModInfo={this.props.isAdmin}
|
showModInfo={this.props.isAdmin}
|
||||||
|
showUsernameBlockAlert={this.state.showUsernameBlockAlert}
|
||||||
singleCommentId={this.state.singleCommentId}
|
singleCommentId={this.state.singleCommentId}
|
||||||
visibilityInfo={this.props.visibilityInfo}
|
visibilityInfo={this.props.visibilityInfo}
|
||||||
onAddComment={this.handleAddComment}
|
onAddComment={this.handleAddComment}
|
||||||
|
|
Loading…
Reference in a new issue