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:
1 parent
46b5e41352
commit
ee80e07ade
9 files changed
+290
-5
No files matched your search
@@ -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,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))
|
||||
});
|
||||
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
Reference in new issue
Block a user