Add telemetry opt-in/opt-out dialog

The playground now displays this on startup when in Scratch Desktop
simulation mode.
This commit is contained in:
Christopher Willis-Ford committed 2018-12-20 13:50:36 -08:00
1 parent 46b5e41352
commit ee80e07ade
9 files changed
+290 -5

No files matched your search

+17
View File
@@ -31,6 +31,7 @@ import Cards from '../../containers/cards.jsx';
import Alerts from '../../containers/alerts.jsx';
import DragLayer from '../../containers/drag-layer.jsx';
import ConnectionModal from '../../containers/connection-modal.jsx';
import TelemetryModal from '../telemetry-modal/telemetry-modal.jsx';
import layout, {STAGE_SIZE_MODES} from '../../lib/layout-constants';
import {resolveStageSize} from '../../lib/screen-utils';
@@ -100,6 +101,7 @@ const GUIComponent = props => {
onExtensionButtonClick,
onRequestCloseBackdropLibrary,
onRequestCloseCostumeLibrary,
onRequestCloseTelemetryModal,
onSeeCommunity,
onShare,
previewInfoVisible,
@@ -107,6 +109,8 @@ const GUIComponent = props => {
soundsTabVisible,
stageSizeMode,
targetIsStage,
telemetryModalHandlers,
telemetryModalVisible,
tipsLibraryVisible,
vm,
...componentProps
@@ -151,6 +155,12 @@ const GUIComponent = props => {
{previewInfoVisible ? (
<PreviewModal />
) : null}
{telemetryModalVisible ? (
<TelemetryModal
{...telemetryModalHandlers}
onRequestClose={onRequestCloseTelemetryModal}
/>
) : null}
{loading ? (
<Loader />
) : null}
@@ -379,6 +389,7 @@ GUIComponent.propTypes = {
onOpenRegistration: PropTypes.func,
onRequestCloseBackdropLibrary: PropTypes.func,
onRequestCloseCostumeLibrary: PropTypes.func,
onRequestCloseTelemetryModal: PropTypes.func,
onSeeCommunity: PropTypes.func,
onShare: PropTypes.func,
onTabSelect: PropTypes.func,
@@ -390,6 +401,12 @@ GUIComponent.propTypes = {
soundsTabVisible: PropTypes.bool,
stageSizeMode: PropTypes.oneOf(Object.keys(STAGE_SIZE_MODES)),
targetIsStage: PropTypes.bool,
telemetryModalHandlers: PropTypes.shape({
onCancel: PropTypes.func,
onOptIn: PropTypes.func.isRequired,
onOptOut: PropTypes.func.isRequired
}),
telemetryModalVisible: PropTypes.bool,
tipsLibraryVisible: PropTypes.bool,
vm: PropTypes.instanceOf(VM).isRequired
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

@@ -0,0 +1,82 @@
@import "../../css/colors.css";
@import "../../css/units.css";
@import "../../css/typography.css";
@import "../../css/z-index.css";
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: $z-index-modal;
background-color: $ui-modal-overlay;
}
.modal-content {
margin: 100px auto;
outline: none;
border: .25rem solid $ui-white-transparent;
padding: 0;
border-radius: $space;
user-select: none;
width: 640px;
color: $text-primary;
overflow: hidden;
}
.illustration {
width: 100%;
height: 128px;
background-color: $motion-primary;
background-image: url('./telemetry-modal-header.png');
background-size: cover;
}
.body {
background: $ui-white;
padding: 1.5rem 2.25rem;
text-align: left;
}
.privacy-policy-link {
color: $motion-primary;
text-decoration: none;
}
/* Confirmation buttons at the bottom of the modal */
.button-row {
margin: 1.5rem 0;
font-weight: bolder;
text-align: right;
display: flex;
justify-content: center;
}
.button-row button {
border: 1px solid $motion-primary;
border-radius: 0.25rem;
padding: 0.5rem 1.5rem;
background: white;
font-weight: bold;
font-size: .875rem;
cursor: pointer;
}
.button-row button.opt-in {
background: $motion-primary;
color: white;
}
.button-row button.opt-out {
color: $motion-primary;
}
[dir="ltr"] .button-row button + button {
margin-left: 0.5rem;
}
[dir="rtl"] .button-row button + button {
margin-right: 0.5rem;
}
@@ -0,0 +1,136 @@
import bindAll from 'lodash.bindall';
import PropTypes from 'prop-types';
import React from 'react';
import {defineMessages, injectIntl, intlShape, FormattedMessage} from 'react-intl';
import ReactModal from 'react-modal';
import Box from '../box/box.jsx';
import styles from './telemetry-modal.css';
const messages = defineMessages({
label: {
id: 'gui.telemetryOptIn.label',
defaultMessage: 'Report statistics to improve Scratch',
description: 'Scratch 3.0 telemetry modal label - for accessibility'
},
bodyText1: {
defaultMessage: 'The Scratch Team is always looking to better understand how Scratch is used around the ' +
'world. To help support this effort, you can allow Scratch to automatically send usage information to ' +
'the Scratch Team.',
description: 'First paragraph of body text for telemetry opt-in modal',
id: 'gui.telemetryOptIn.body1'
},
bodyText2: {
defaultMessage: 'The information we collect includes language selection, blocks usage, and some events like ' +
'saving, loading, and uploading a project. We DO NOT collect any personal information. Please see our ' +
'{privacyPolicyLink} for more information.',
description: 'First paragraph of body text for telemetry opt-in modal',
id: 'gui.telemetryOptIn.body2'
},
privacyPolicyLink: {
defaultMessage: 'Privacy Policy',
description: 'Link to the Scratch privacy policy',
id: 'gui.telemetryOptIn.privacyPolicyLink'
},
noButton: {
defaultMessage: 'No, thanks',
description: 'Text for telemetry modal opt-out button',
id: 'gui.telemetryOptIn.buttonTextNo'
},
noTooltip: {
defaultMessage: 'Disable telemetry',
description: 'Tooltip for telemetry modal opt-out button',
id: 'gui.telemetryOptIn.buttonTooltipNo'
},
yesButton: {
defaultMessage: "Yes, I'd like to help improve Scratch",
description: 'Text for telemetry modal opt-in button',
id: 'gui.telemetryOptIn.buttonTextYes'
},
yesTooltip: {
defaultMessage: 'Enable telemetry',
description: 'Tooltip for telemetry modal opt-in button',
id: 'gui.telemetryOptIn.buttonTooltipYes'
}
});
class TelemetryModal extends React.PureComponent {
constructor (props) {
super(props);
bindAll(this, [
'handleCancel',
'handleOptIn',
'handleOptOut'
]);
}
handleCancel () {
this.props.onRequestClose();
if (this.props.onCancel) {
this.props.onCancel();
}
}
handleOptIn () {
this.props.onRequestClose();
this.props.onOptIn();
}
handleOptOut () {
this.props.onRequestClose();
this.props.onOptOut();
}
render () {
return (<ReactModal
isOpen
className={styles.modalContent}
contentLabel={this.props.intl.formatMessage(messages.label)}
overlayClassName={styles.modalOverlay}
onRequestClose={this.handleCancel}
>
<div dir={this.props.isRtl ? 'rtl' : 'ltr'} >
<Box className={styles.illustration} />
<Box className={styles.body}>
<p><FormattedMessage {...messages.bodyText1} /></p>
<p><FormattedMessage
{...messages.bodyText2}
values={{
privacyPolicyLink: (<a
className={styles.privacyPolicyLink}
href="https://scratch.mit.edu/privacy_policy/"
>
<FormattedMessage {...messages.privacyPolicyLink} />
</a>)
}}
/></p>
<Box className={styles.buttonRow}>
<button
className={styles.optOut}
title={this.props.intl.formatMessage(messages.noTooltip)}
onClick={this.handleOptOut}
>
<FormattedMessage {...messages.noButton} />
</button>
<button
className={styles.optIn}
title={this.props.intl.formatMessage(messages.yesTooltip)}
onClick={this.handleOptIn}
>
<FormattedMessage {...messages.yesButton} />
</button>
</Box>
</Box>
</div>
</ReactModal>);
}
}
TelemetryModal.propTypes = {
intl: intlShape.isRequired,
isRtl: PropTypes.bool,
onCancel: PropTypes.func,
onOptIn: PropTypes.func.isRequired,
onOptOut: PropTypes.func.isRequired,
onRequestClose: PropTypes.func
};
export default injectIntl(TelemetryModal);
+7 -3
View File
@@ -7,7 +7,6 @@ import VM from 'scratch-vm';
import {defineMessages, injectIntl, intlShape} from 'react-intl';
import ErrorBoundaryHOC from '../lib/error-boundary-hoc.jsx';
import {openExtensionLibrary} from '../reducers/modals';
import {
getIsError,
getIsShowingProject
@@ -22,7 +21,9 @@ import {
import {
closeCostumeLibrary,
closeBackdropLibrary
closeBackdropLibrary,
closeTelemetryModal,
openExtensionLibrary
} from '../reducers/modals';
import FontLoaderHOC from '../lib/font-loader-hoc.jsx';
@@ -128,6 +129,7 @@ GUI.propTypes = {
projectHost: PropTypes.string,
projectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
projectTitle: PropTypes.string,
telemetryModalVisible: PropTypes.bool,
vm: PropTypes.instanceOf(VM).isRequired
};
@@ -157,11 +159,12 @@ const mapStateToProps = state => {
loadingStateVisible: state.scratchGui.modals.loadingProject,
previewInfoVisible: state.scratchGui.modals.previewInfo,
projectId: state.scratchGui.projectState.projectId,
soundsTabVisible: state.scratchGui.editorTab.activeTabIndex === SOUNDS_TAB_INDEX,
targetIsStage: (
state.scratchGui.targets.stage &&
state.scratchGui.targets.stage.id === state.scratchGui.targets.editingTarget
),
soundsTabVisible: state.scratchGui.editorTab.activeTabIndex === SOUNDS_TAB_INDEX,
telemetryModalVisible: state.scratchGui.modals.telemetryModal,
tipsLibraryVisible: state.scratchGui.modals.tipsLibrary,
vm: state.scratchGui.vm
};
@@ -174,6 +177,7 @@ const mapDispatchToProps = dispatch => ({
onActivateSoundsTab: () => dispatch(activateTab(SOUNDS_TAB_INDEX)),
onRequestCloseBackdropLibrary: () => dispatch(closeBackdropLibrary()),
onRequestCloseCostumeLibrary: () => dispatch(closeCostumeLibrary()),
onRequestCloseTelemetryModal: () => dispatch(closeTelemetryModal()),
onUpdateReduxProjectTitle: title => dispatch(setProjectTitle(title))
});
+5 -1
View File
@@ -51,7 +51,8 @@ const AppStateHOC = function (WrappedComponent, localesOnly) {
guiMiddleware,
initFullScreen,
initPlayer,
initPreviewInfo
initPreviewInfo,
initTelemetryModal
} = guiRedux;
const {ScratchPaintReducer} = require('scratch-paint');
@@ -63,6 +64,8 @@ const AppStateHOC = function (WrappedComponent, localesOnly) {
if (props.isPlayerOnly) {
initializedGui = initPlayer(initializedGui);
}
} else if (props.showTelemetryModal) {
initializedGui = initTelemetryModal(initializedGui);
} else if (props.showPreviewInfo) {
initializedGui = initPreviewInfo(initializedGui);
}
@@ -98,6 +101,7 @@ const AppStateHOC = function (WrappedComponent, localesOnly) {
isFullScreen, // eslint-disable-line no-unused-vars
isPlayerOnly, // eslint-disable-line no-unused-vars
showPreviewInfo, // eslint-disable-line no-unused-vars
showTelemetryModal, // eslint-disable-line no-unused-vars
...componentProps
} = this.props;
return (
+19
View File
@@ -6,11 +6,24 @@ import AppStateHOC from '../lib/app-state-hoc.jsx';
import GUI from '../containers/gui.jsx';
import HashParserHOC from '../lib/hash-parser-hoc.jsx';
import TitledHOC from '../lib/titled-hoc.jsx';
import log from '../lib/log.js';
const onClickLogo = () => {
window.location = 'https://scratch.mit.edu';
};
const handleTelemetryModalCancel = () => {
log('User canceled telemetry modal');
};
const handleTelemetryModalOptIn = () => {
log('User opted into telemetry');
};
const handleTelemetryModalOptOut = () => {
log('User opted out of telemetry');
};
/*
* Render the GUI playground. This is a separate function because importing anything
* that instantiates the VM causes unsupported browsers to crash
@@ -55,7 +68,13 @@ export default appTarget => {
simulateScratchDesktop ?
<WrappedGui
isScratchDesktop
showTelemetryModal
canSave={false}
telemetryModalHandlers={{
onCancel: handleTelemetryModalCancel,
onOptIn: handleTelemetryModalOptIn,
onOptOut: handleTelemetryModalOptOut
}}
/> :
<WrappedGui
backpackVisible
+14 -1
View File
@@ -122,7 +122,19 @@ const initPreviewInfo = function (currentState) {
currentState,
{
modals: {
previewInfo: true
previewInfo: true // this key must match `MODAL_PREVIEW_INFO` in modals.js
}
}
);
};
const initTelemetryModal = function (currentState) {
return Object.assign(
{},
currentState,
{
modals: {
telemetryModal: true // this key must match `MODAL_TELEMETRY` in modals.js
}
}
);
@@ -165,5 +177,6 @@ export {
initFullScreen,
initPlayer,
initPreviewInfo,
initTelemetryModal,
initTutorialCard
};
+10
View File
@@ -8,6 +8,7 @@ const MODAL_EXTENSION_LIBRARY = 'extensionLibrary';
const MODAL_IMPORT_INFO = 'importInfo';
const MODAL_LOADING_PROJECT = 'loadingProject';
const MODAL_PREVIEW_INFO = 'previewInfo';
const MODAL_TELEMETRY = 'telemetryModal';
const MODAL_SOUND_LIBRARY = 'soundLibrary';
const MODAL_SPRITE_LIBRARY = 'spriteLibrary';
const MODAL_SOUND_RECORDER = 'soundRecorder';
@@ -22,6 +23,7 @@ const initialState = {
[MODAL_IMPORT_INFO]: false,
[MODAL_LOADING_PROJECT]: false,
[MODAL_PREVIEW_INFO]: false,
[MODAL_TELEMETRY]: false,
[MODAL_SOUND_LIBRARY]: false,
[MODAL_SPRITE_LIBRARY]: false,
[MODAL_SOUND_RECORDER]: false,
@@ -77,6 +79,9 @@ const openLoadingProject = function () {
const openPreviewInfo = function () {
return openModal(MODAL_PREVIEW_INFO);
};
const openTelemetryModal = function () {
return openModal(MODAL_TELEMETRY);
};
const openSoundLibrary = function () {
return openModal(MODAL_SOUND_LIBRARY);
};
@@ -113,6 +118,9 @@ const closeLoadingProject = function () {
const closePreviewInfo = function () {
return closeModal(MODAL_PREVIEW_INFO);
};
const closeTelemetryModal = function () {
return closeModal(MODAL_TELEMETRY);
};
const closeSpriteLibrary = function () {
return closeModal(MODAL_SPRITE_LIBRARY);
};
@@ -141,6 +149,7 @@ export {
openSoundLibrary,
openSpriteLibrary,
openSoundRecorder,
openTelemetryModal,
openTipsLibrary,
openConnectionModal,
closeBackdropLibrary,
@@ -153,6 +162,7 @@ export {
closeSpriteLibrary,
closeSoundLibrary,
closeSoundRecorder,
closeTelemetryModal,
closeTipsLibrary,
closeConnectionModal
};