Merge pull request #7165 from aoneill01/feature/disable-cloud-vars-when-video-sensing

feat: Disable cloud variables when camera is active
This commit is contained in:
Andy O'Neill 2022-11-14 09:11:42 -05:00 committed by GitHub
commit 8a5e0637c1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 366 additions and 17 deletions

View file

@ -20,7 +20,8 @@ module.exports = {
const stage = project.targets[0];
return Object.values(stage.variables)
.some(variable => variable.length === 3); // 3 entries if cloud var
}
},
videoSensing: project => (project.extensions || []).includes('videoSensing')
},
2: {
extensions: () => [], // Showing extension chip not implemented for scratch2 projects
@ -30,6 +31,11 @@ module.exports = {
// 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
cloudData: project => project.info.hasCloudData,
videoSensing: project => {
const stringifiedProject = JSON.stringify(project);
return ['senseVideoMotion', 'setVideoState', 'setVideoTransparency', 'whenSensorGreaterThan']
.some(opcode => stringifiedProject.includes(`["${opcode}"`));
}
}
};

View file

@ -47,5 +47,6 @@
"project.cloudDataLink": "See Data",
"project.usernameBlockAlert": "This project can detect who is using it, through the \"username\" block. To hide your identity, sign out before using the project.",
"project.inappropriateUpdate": "Hmm...the bad word detector thinks there is a problem with your text. Please change it and remember to be respectful.",
"project.mutedAddToStudio": "You will be able to add to studios again {inDuration}."
"project.mutedAddToStudio": "You will be able to add to studios again {inDuration}.",
"project.cloudDataAndVideoAlert": "For privacy reasons, cloud variables have been disabled in this project because it contains video sensing blocks."
}

View file

@ -125,6 +125,7 @@ const PreviewPresentation = ({
originalInfo,
parentInfo,
showCloudDataAlert,
showCloudDataAndVideoAlert,
showUsernameBlockAlert,
projectHost,
projectId,
@ -335,16 +336,23 @@ const PreviewPresentation = ({
{fullscreen: isFullScreen}
)}
>
{showCloudDataAlert && (
<FlexRow className="project-info-alert">
<FormattedMessage id="project.cloudDataAlert" />
</FlexRow>
)}
{showUsernameBlockAlert && (
<FlexRow className="project-info-alert">
<FormattedMessage id="project.usernameBlockAlert" />
</FlexRow>
)}
<div className="project-info-alerts">
{showCloudDataAlert && (
<FlexRow className="project-info-alert">
<FormattedMessage id="project.cloudDataAlert" />
</FlexRow>
)}
{showCloudDataAndVideoAlert && (
<FlexRow className="project-info-alert">
<FormattedMessage id="project.cloudDataAndVideoAlert" />
</FlexRow>
)}
{showUsernameBlockAlert && (
<FlexRow className="project-info-alert">
<FormattedMessage id="project.usernameBlockAlert" />
</FlexRow>
)}
</div>
<IntlGUI
isPlayerOnly
assetHost={assetHost}
@ -785,6 +793,7 @@ PreviewPresentation.propTypes = {
reportOpen: PropTypes.bool,
showAdminPanel: PropTypes.bool,
showCloudDataAlert: PropTypes.bool,
showCloudDataAndVideoAlert: PropTypes.bool,
showEmailConfirmationModal: PropTypes.bool,
showEmailConfirmationBanner: PropTypes.bool,
showModInfo: PropTypes.bool,

View file

@ -367,10 +367,17 @@ $stage-width: 480px;
z-index: 1;
$alert-bg: rgba(255, 255, 255, .85);
.project-info-alert {
.project-info-alerts {
position: absolute;
z-index: 8; // Below navbar
margin: 60px 15px;
margin: 60px 15px 0;
display: flex;
flex-direction: column;
gap: 15px;
}
.project-info-alert {
border-radius: .25rem;
background: $alert-bg;
padding: .75rem;

View file

@ -379,9 +379,10 @@ class Preview extends React.Component {
}
if (showAlerts) {
// Check for username block only if user is logged in
// Check for username and video blocks only if user is logged in
if (this.props.isLoggedIn) {
newState.showUsernameBlockAlert = helpers.usernameBlock(projectData[0]);
newState.showCloudDataAndVideoAlert = hasCloudData && helpers.videoSensing(projectData[0]);
} else { // Check for cloud vars only if user is logged out
newState.showCloudDataAlert = hasCloudData;
}
@ -492,6 +493,7 @@ class Preview extends React.Component {
this.setState({
showUsernameBlockAlert: false,
showCloudDataAlert: false,
showCloudDataAndVideoAlert: false,
greenFlagRecorded: true
});
}
@ -607,7 +609,8 @@ class Preview extends React.Component {
handleSeeInside () {
this.setState({ // Remove any project alerts so they don't show up later
showUsernameBlockAlert: false,
showCloudDataAlert: false
showCloudDataAlert: false,
showCloudDataAndVideoAlert: false
});
this.props.setPlayer(false);
if (this.state.justRemixed || this.state.justShared) {
@ -794,6 +797,7 @@ class Preview extends React.Component {
reportOpen={this.state.reportOpen}
showAdminPanel={this.props.isAdmin}
showCloudDataAlert={this.state.showCloudDataAlert}
showCloudDataAndVideoAlert={this.state.showCloudDataAndVideoAlert}
showModInfo={this.props.isAdmin}
showEmailConfirmationModal={this.state.showEmailConfirmationModal}
showEmailConfirmationBanner={this.props.showEmailConfirmationBanner}

View file

@ -0,0 +1,322 @@
/* eslint-disable no-use-before-define */
const projectInfo = require('../../../src/lib/project-info');
describe('unit test lib/project-info.js', () => {
test('videoSensing returns true for a version 3 project with video', () => {
const result = projectInfo[videoVersion3.projectVersion].videoSensing(videoVersion3);
expect(result).toEqual(true);
});
test('videoSensing returns false for a version 3 project with no video', () => {
const result = projectInfo[noVideoVersion3.projectVersion].videoSensing(noVideoVersion3);
expect(result).toEqual(false);
});
test('videoSensing returns true for a version 2 project with video', () => {
const result = projectInfo[videoVersion2.projectVersion].videoSensing(videoVersion2);
expect(result).toEqual(true);
});
});
const videoVersion3 = {
targets: [
{
isStage: true,
name: 'Stage',
variables: {
'`jEk@4|i[#Fk?(8x)AV.-my variable': [
'my variable',
'0'
]
},
lists: {},
broadcasts: {},
blocks: {
'FJz[,QI8`P^5;FEjdBhc': {
opcode: 'event_whenflagclicked',
next: 'f8q%j#X8sU#C+E1z|-oF',
parent: null,
inputs: {},
fields: {},
shadow: false,
topLevel: true,
x: 255,
y: 171
},
'f8q%j#X8sU#C+E1z|-oF': {
opcode: 'videoSensing_videoToggle',
next: null,
parent: 'FJz[,QI8`P^5;FEjdBhc',
inputs: {
VIDEO_STATE: [
1,
'a2$KXEUlr`=IW!MX8(M7'
]
},
fields: {},
shadow: false,
topLevel: false
},
'a2$KXEUlr`=IW!MX8(M7': {
opcode: 'videoSensing_menu_VIDEO_STATE',
next: null,
parent: 'f8q%j#X8sU#C+E1z|-oF',
inputs: {},
fields: {
VIDEO_STATE: [
'on',
null
]
},
shadow: true,
topLevel: false
}
},
comments: {},
currentCostume: 0,
costumes: [
{
name: 'backdrop1',
dataFormat: 'svg',
assetId: 'cd21514d0531fdffb22204e0ec5ed84a',
md5ext: 'cd21514d0531fdffb22204e0ec5ed84a.svg',
rotationCenterX: 240,
rotationCenterY: 180
}
],
sounds: [
{
name: 'pop',
assetId: '83a9787d4cb6f3b7632b4ddfebf74367',
dataFormat: 'wav',
format: '',
rate: 48000,
sampleCount: 1123,
md5ext: '83a9787d4cb6f3b7632b4ddfebf74367.wav'
}
],
volume: 100,
layerOrder: 0,
tempo: 60,
videoTransparency: 50,
videoState: 'on',
textToSpeechLanguage: null
}
],
monitors: [
{
id: '`jEk@4|i[#Fk?(8x)AV.-my variable',
mode: 'default',
opcode: 'data_variable',
params: {
VARIABLE: 'my variable'
},
spriteName: null,
value: '0',
width: 0,
height: 0,
x: 7,
y: 17,
visible: false,
sliderMin: 0,
sliderMax: 100,
isDiscrete: true
}
],
extensions: [
'videoSensing'
],
meta: {
semver: '3.0.0',
vm: '1.2.48',
// eslint-disable-next-line max-len
agent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36'
},
projectVersion: 3
};
const noVideoVersion3 = {
targets: [
{
isStage: true,
name: 'Stage',
variables: {
'`jEk@4|i[#Fk?(8x)AV.-my variable': [
'my variable',
'0'
]
},
lists: {},
broadcasts: {},
blocks: {
'FJz[,QI8`P^5;FEjdBhc': {
opcode: 'event_whenflagclicked',
next: '`CA90wtKfX0xa.mK80[|',
parent: null,
inputs: {},
fields: {},
shadow: false,
topLevel: true,
x: 255,
y: 171
},
'`CA90wtKfX0xa.mK80[|': {
opcode: 'data_setvariableto',
next: null,
parent: 'FJz[,QI8`P^5;FEjdBhc',
inputs: {
VALUE: [
1,
[
10,
'0'
]
]
},
fields: {
VARIABLE: [
'my variable',
'`jEk@4|i[#Fk?(8x)AV.-my variable'
]
},
shadow: false,
topLevel: false
}
},
comments: {},
currentCostume: 0,
costumes: [
{
name: 'backdrop1',
dataFormat: 'svg',
assetId: 'cd21514d0531fdffb22204e0ec5ed84a',
md5ext: 'cd21514d0531fdffb22204e0ec5ed84a.svg',
rotationCenterX: 240,
rotationCenterY: 180
}
],
sounds: [
{
name: 'pop',
assetId: '83a9787d4cb6f3b7632b4ddfebf74367',
dataFormat: 'wav',
format: '',
rate: 48000,
sampleCount: 1123,
md5ext: '83a9787d4cb6f3b7632b4ddfebf74367.wav'
}
],
volume: 100,
layerOrder: 0,
tempo: 60,
videoTransparency: 50,
videoState: 'on',
textToSpeechLanguage: null
}
],
monitors: [
{
id: '`jEk@4|i[#Fk?(8x)AV.-my variable',
mode: 'default',
opcode: 'data_variable',
params: {
VARIABLE: 'my variable'
},
spriteName: null,
value: '0',
width: 0,
height: 0,
x: 7,
y: 17,
visible: false,
sliderMin: 0,
sliderMax: 100,
isDiscrete: true
}
],
extensions: [],
meta: {
semver: '3.0.0',
vm: '1.2.48',
// eslint-disable-next-line max-len
agent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36'
},
projectVersion: 3
};
const videoVersion2 = {
objName: 'Stage',
sounds: [{
soundName: 'pop',
soundID: 1,
md5: '83a9787d4cb6f3b7632b4ddfebf74367.wav',
sampleCount: 258,
rate: 11025,
format: ''
}],
costumes: [{
costumeName: 'backdrop1',
baseLayerID: 3,
baseLayerMD5: '739b5e2a2435f6e1ec2993791b423146.png',
bitmapResolution: 1,
rotationCenterX: 240,
rotationCenterY: 180
}],
currentCostumeIndex: 0,
penLayerMD5: '5c81a336fab8be57adc039a8a2b33ca9.png',
penLayerID: 0,
tempoBPM: 60,
videoAlpha: 0.5,
children: [{
objName: 'Sprite1',
scripts: [[62,
85,
[['whenGreenFlag'], ['doForever', [['say:', ['senseVideoMotion', 'motion', 'this sprite']]]]]],
[70, 216, [['setVideoState', 'on']]],
[66, 281, [['setVideoTransparency', 50]]]],
sounds: [{
soundName: 'meow',
soundID: 0,
md5: '83c36d806dc92327b9e7049a565c6bff.wav',
sampleCount: 18688,
rate: 22050,
format: ''
}],
costumes: [{
costumeName: 'costume1',
baseLayerID: 1,
baseLayerMD5: 'f9a1c175dbe2e5dee472858dd30d16bb.svg',
bitmapResolution: 1,
rotationCenterX: 47,
rotationCenterY: 55
},
{
costumeName: 'costume2',
baseLayerID: 2,
baseLayerMD5: '6e8bd9ae68fdb02b7e1e3df656a75635.svg',
bitmapResolution: 1,
rotationCenterX: 47,
rotationCenterY: 55
}],
currentCostumeIndex: 0,
scratchX: 0,
scratchY: 0,
scale: 1,
direction: 90,
rotationStyle: 'normal',
isDraggable: false,
indexInLibrary: 1,
visible: true,
spriteInfo: {
}
}],
info: {
userAgent: 'Scratch 2.0 Offline Editor',
flashVersion: 'WIN 33,1,1,743',
spriteCount: 1,
videoOn: false,
scriptCount: 1,
swfVersion: 'v461'
},
projectVersion: 2
};