From 628e00f4016725b3b3b8fba32d4231e33de6fde8 Mon Sep 17 00:00:00 2001
From: Ben Wheeler
Date: Mon, 16 Sep 2019 22:34:32 -0400
Subject: [PATCH 01/38] when select dropdown is focused, clear any validation
errors
---
src/components/join-flow/birthdate-step.jsx | 9 ++++++++-
src/components/join-flow/country-step.jsx | 6 +++++-
2 files changed, 13 insertions(+), 2 deletions(-)
diff --git a/src/components/join-flow/birthdate-step.jsx b/src/components/join-flow/birthdate-step.jsx
index e22c7f22c..b4a726862 100644
--- a/src/components/join-flow/birthdate-step.jsx
+++ b/src/components/join-flow/birthdate-step.jsx
@@ -84,7 +84,8 @@ class BirthDateStep extends React.Component {
const {
errors,
handleSubmit,
- isSubmitting
+ isSubmitting,
+ setFieldError
} = props;
return (
setFieldError('birth_month', null)}
+ /* eslint-enable react/jsx-no-bind */
/>
setFieldError('birth_year', null)}
+ /* eslint-enable react/jsx-no-bind */
/>
diff --git a/src/components/join-flow/country-step.jsx b/src/components/join-flow/country-step.jsx
index fbe53a7c4..ee9854250 100644
--- a/src/components/join-flow/country-step.jsx
+++ b/src/components/join-flow/country-step.jsx
@@ -64,7 +64,8 @@ class CountryStep extends React.Component {
const {
errors,
handleSubmit,
- isSubmitting
+ isSubmitting,
+ setFieldError
} = props;
return (
setFieldError('country', null)}
+ /* eslint-enable react/jsx-no-bind */
/>
{/* note that this is a hidden checkbox the user will never see */}
Date: Wed, 18 Sep 2019 10:26:37 -0400
Subject: [PATCH 02/38] Handle errors of captcha loading by setting error state
on JoinFlow.
---
src/components/join-flow/email-step.jsx | 11 +++++--
src/components/join-flow/join-flow.jsx | 10 +++++++
src/l10n.json | 1 +
test/unit/components/email-step.test.jsx | 37 ++++++++++++++++++++++++
test/unit/components/join-flow.test.jsx | 13 +++++++++
5 files changed, 69 insertions(+), 3 deletions(-)
diff --git a/src/components/join-flow/email-step.jsx b/src/components/join-flow/email-step.jsx
index f2d1e42a3..15ae71872 100644
--- a/src/components/join-flow/email-step.jsx
+++ b/src/components/join-flow/email-step.jsx
@@ -56,7 +56,11 @@ class EmailStep extends React.Component {
this.emailInput = emailInputRef;
}
onCaptchaError () {
- // TODO send user to error step once we have one.
+ this.props.onMidRegistrationError(
+ this.props.intl.formatMessage({
+ id: 'registation.troubleReload'
+ })
+ );
}
onCaptchaLoad () {
this.setState({captchaIsLoading: false});
@@ -64,9 +68,9 @@ class EmailStep extends React.Component {
if (!this.grecaptcha) {
// According to the reCaptcha documentation, this callback shouldn't get
// called unless window.grecaptcha exists. This is just here to be extra defensive.
- // TODO: Put up the error screen when we have one.
+ this.onCaptchaError();
+ return;
}
- // TODO: Add in error callback for render once we have an error screen.
this.widgetId = this.grecaptcha.render(this.captchaRef,
{
callback: this.captchaSolved,
@@ -207,6 +211,7 @@ class EmailStep extends React.Component {
EmailStep.propTypes = {
intl: intlShape,
+ onMidRegistrationError: PropTypes.func,
onNextStep: PropTypes.func,
waiting: PropTypes.bool
};
diff --git a/src/components/join-flow/join-flow.jsx b/src/components/join-flow/join-flow.jsx
index 6b18d6a40..5eb8641be 100644
--- a/src/components/join-flow/join-flow.jsx
+++ b/src/components/join-flow/join-flow.jsx
@@ -23,6 +23,7 @@ class JoinFlow extends React.Component {
super(props);
bindAll(this, [
'handleAdvanceStep',
+ 'handleMidRegistrationError',
'handlePrepareToRegister',
'handleRegistrationResponse',
'handleSubmitRegistration'
@@ -34,6 +35,14 @@ class JoinFlow extends React.Component {
waiting: false
};
}
+ handleMidRegistrationError (message) {
+ if (!message) {
+ message = this.props.intl.formatMessage({
+ id: 'registration.generalError'
+ });
+ }
+ this.setState({registrationError: message});
+ }
handlePrepareToRegister (newFormData) {
newFormData = newFormData || {};
const newState = {
@@ -142,6 +151,7 @@ class JoinFlow extends React.Component {
{
expect(emailInputWrapper.props().onSetRef).toEqual(formikWrapper.instance().handleSetEmailRef);
expect(emailInputWrapper.props().validate).toEqual(formikWrapper.instance().validateEmail);
});
+
test('props sent to FormikCheckbox for subscribe', () => {
const wrapper = shallowWithIntl();
// Dive to get past the intl wrapper
@@ -63,6 +64,7 @@ describe('EmailStep test', () => {
expect(checkboxWrapper.first().props().label).toEqual('registration.receiveEmails');
expect(checkboxWrapper.first().props().name).toEqual('subscribe');
});
+
test('handleValidSubmit passes formData to next step', () => {
const formikBag = {
setSubmitting: jest.fn()
@@ -82,6 +84,7 @@ describe('EmailStep test', () => {
expect(formikBag.setSubmitting).toHaveBeenCalledWith(false);
expect(global.grecaptcha.execute).toHaveBeenCalled();
});
+
test('captchaSolved sets token and goes to next step', () => {
const props = {
onNextStep: jest.fn()
@@ -116,6 +119,38 @@ describe('EmailStep test', () => {
}));
expect(formikBag.setSubmitting).toHaveBeenCalledWith(true);
});
+
+ test('onCaptchaError calls error function with correct message', () => {
+ const props = {
+ onMidRegistrationError: jest.fn()
+ };
+
+ const wrapper = shallowWithIntl(
+ );
+
+ const formikWrapper = wrapper.dive();
+ formikWrapper.instance().onCaptchaError();
+ expect(props.onMidRegistrationError).toHaveBeenCalledWith('registation.troubleReload');
+ });
+
+ test('Captcha load error calls error function', () => {
+ const props = {
+ onMidRegistrationError: jest.fn()
+ };
+ // Set this to null to force an error.
+ global.grecaptcha = null;
+ const wrapper = shallowWithIntl(
+ );
+
+ const formikWrapper = wrapper.dive();
+ formikWrapper.instance().onCaptchaLoad();
+ expect(props.onMidRegistrationError).toHaveBeenCalledWith('registation.troubleReload');
+ });
+
test('validateEmail test email empty', () => {
const wrapper = shallowWithIntl(
);
@@ -123,6 +158,7 @@ describe('EmailStep test', () => {
const val = formikWrapper.instance().validateEmail('');
expect(val).toBe('general.required');
});
+
test('validateEmail test email null', () => {
const wrapper = shallowWithIntl(
);
@@ -130,6 +166,7 @@ describe('EmailStep test', () => {
const val = formikWrapper.instance().validateEmail(null);
expect(val).toBe('general.required');
});
+
test('validateEmail test email undefined', () => {
const wrapper = shallowWithIntl(
);
diff --git a/test/unit/components/join-flow.test.jsx b/test/unit/components/join-flow.test.jsx
index 80dfe5a14..2714ae317 100644
--- a/test/unit/components/join-flow.test.jsx
+++ b/test/unit/components/join-flow.test.jsx
@@ -126,6 +126,19 @@ describe('JoinFlow', () => {
expect(joinFlowInstance.props.refreshSession).not.toHaveBeenCalled();
expect(joinFlowInstance.state.registrationError).toBe('registration.generalError (400)');
});
+ test('handleMidRegistrationError with no message ', () => {
+ const joinFlowInstance = getJoinFlowWrapper().instance();
+ joinFlowInstance.setState({});
+ joinFlowInstance.handleMidRegistrationError();
+ expect(joinFlowInstance.state.registrationError).toBe('registration.generalError');
+ });
+
+ test('handleMidRegistrationError with message ', () => {
+ const joinFlowInstance = getJoinFlowWrapper().instance();
+ joinFlowInstance.setState({});
+ joinFlowInstance.handleMidRegistrationError('my message');
+ expect(joinFlowInstance.state.registrationError).toBe('my message');
+ });
test('handleAdvanceStep', () => {
const joinFlowInstance = getJoinFlowWrapper().instance();
From b98f8eda72757666d81f9f4ae8ccdb4b07d7dd83 Mon Sep 17 00:00:00 2001
From: picklesrus
Date: Thu, 19 Sep 2019 13:12:50 -0400
Subject: [PATCH 03/38] Hopefully fix captcha site key in prod. I think I made
a copy/paste error originally.
---
.travis.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.travis.yml b/.travis.yml
index b60270840..8879cfdf6 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -35,7 +35,7 @@ env:
- CLOUDDATA_HOST=${CLOUDDATA_HOST:-$CLOUDDATA_HOST_STAGING}
- RECAPTCHA_SITE_KEY_master=6LeRbUwUAAAAAFYhKgk3G9OKWqE_OJ7Z-7VTUCbl
- RECAPTCHA_SITE_KEY_STAGING=6LfukK4UAAAAAFR44yoZMhv8fj6xh-PMiIxwryG3
- - RECAPTCHA_SITE=RECAPTCHA_SITE_KEY_$TRAVIS_BRANCH
+ - RECAPTCHA_SITE_KEY_VAR=RECAPTCHA_SITE_KEY_$TRAVIS_BRANCH
- RECAPTCHA_SITE_KEY=${!RECAPTCHA_SITE_KEY_VAR}
- RECAPTCHA_SITE_KEY=${RECAPTCHA_SITE_KEY:-$RECAPTCHA_SITE_KEY_STAGING}
- ROOT_URL_master=https://scratch.mit.edu
From cdd90da423e59e825983858748617f85b29ba783 Mon Sep 17 00:00:00 2001
From: picklesrus
Date: Thu, 19 Sep 2019 13:40:09 -0400
Subject: [PATCH 04/38] Rename error function.
---
src/components/join-flow/email-step.jsx | 4 ++--
src/components/join-flow/join-flow.jsx | 6 +++---
test/unit/components/email-step.test.jsx | 8 ++++----
test/unit/components/join-flow.test.jsx | 8 ++++----
4 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/src/components/join-flow/email-step.jsx b/src/components/join-flow/email-step.jsx
index 15ae71872..803bd1bf8 100644
--- a/src/components/join-flow/email-step.jsx
+++ b/src/components/join-flow/email-step.jsx
@@ -56,7 +56,7 @@ class EmailStep extends React.Component {
this.emailInput = emailInputRef;
}
onCaptchaError () {
- this.props.onMidRegistrationError(
+ this.props.onRegistrationError(
this.props.intl.formatMessage({
id: 'registation.troubleReload'
})
@@ -211,8 +211,8 @@ class EmailStep extends React.Component {
EmailStep.propTypes = {
intl: intlShape,
- onMidRegistrationError: PropTypes.func,
onNextStep: PropTypes.func,
+ onRegistrationError: PropTypes.func,
waiting: PropTypes.bool
};
diff --git a/src/components/join-flow/join-flow.jsx b/src/components/join-flow/join-flow.jsx
index 5eb8641be..6849531e6 100644
--- a/src/components/join-flow/join-flow.jsx
+++ b/src/components/join-flow/join-flow.jsx
@@ -23,7 +23,7 @@ class JoinFlow extends React.Component {
super(props);
bindAll(this, [
'handleAdvanceStep',
- 'handleMidRegistrationError',
+ 'handleRegistrationError',
'handlePrepareToRegister',
'handleRegistrationResponse',
'handleSubmitRegistration'
@@ -35,7 +35,7 @@ class JoinFlow extends React.Component {
waiting: false
};
}
- handleMidRegistrationError (message) {
+ handleRegistrationError (message) {
if (!message) {
message = this.props.intl.formatMessage({
id: 'registration.generalError'
@@ -151,8 +151,8 @@ class JoinFlow extends React.Component {
{
test('onCaptchaError calls error function with correct message', () => {
const props = {
- onMidRegistrationError: jest.fn()
+ onRegistrationError: jest.fn()
};
const wrapper = shallowWithIntl(
@@ -132,12 +132,12 @@ describe('EmailStep test', () => {
const formikWrapper = wrapper.dive();
formikWrapper.instance().onCaptchaError();
- expect(props.onMidRegistrationError).toHaveBeenCalledWith('registation.troubleReload');
+ expect(props.onRegistrationError).toHaveBeenCalledWith('registation.troubleReload');
});
test('Captcha load error calls error function', () => {
const props = {
- onMidRegistrationError: jest.fn()
+ onRegistrationError: jest.fn()
};
// Set this to null to force an error.
global.grecaptcha = null;
@@ -148,7 +148,7 @@ describe('EmailStep test', () => {
const formikWrapper = wrapper.dive();
formikWrapper.instance().onCaptchaLoad();
- expect(props.onMidRegistrationError).toHaveBeenCalledWith('registation.troubleReload');
+ expect(props.onRegistrationError).toHaveBeenCalledWith('registation.troubleReload');
});
test('validateEmail test email empty', () => {
diff --git a/test/unit/components/join-flow.test.jsx b/test/unit/components/join-flow.test.jsx
index 2714ae317..04cb1a6dc 100644
--- a/test/unit/components/join-flow.test.jsx
+++ b/test/unit/components/join-flow.test.jsx
@@ -126,17 +126,17 @@ describe('JoinFlow', () => {
expect(joinFlowInstance.props.refreshSession).not.toHaveBeenCalled();
expect(joinFlowInstance.state.registrationError).toBe('registration.generalError (400)');
});
- test('handleMidRegistrationError with no message ', () => {
+ test('handleRegistrationError with no message ', () => {
const joinFlowInstance = getJoinFlowWrapper().instance();
joinFlowInstance.setState({});
- joinFlowInstance.handleMidRegistrationError();
+ joinFlowInstance.handleRegistrationError();
expect(joinFlowInstance.state.registrationError).toBe('registration.generalError');
});
- test('handleMidRegistrationError with message ', () => {
+ test('handleRegistrationError with message ', () => {
const joinFlowInstance = getJoinFlowWrapper().instance();
joinFlowInstance.setState({});
- joinFlowInstance.handleMidRegistrationError('my message');
+ joinFlowInstance.handleRegistrationError('my message');
expect(joinFlowInstance.state.registrationError).toBe('my message');
});
From aa6e85d863b4e7cd6460fb7e90771f3247c8116d Mon Sep 17 00:00:00 2001
From: Paul Kaplan
Date: Fri, 20 Sep 2019 08:59:26 -0400
Subject: [PATCH 05/38] Include canonical link in project meta tags
This will tell crawlers that this page and all its exact duplicates (/fullscreen, /editor, /embed, etc...) should be considered the same and use the project page as the canonical crawled page. We had an issue before where by not selected a canonical, google was considering all the project pages duplicates :(
---
src/views/preview/meta.jsx | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/views/preview/meta.jsx b/src/views/preview/meta.jsx
index 57d0758a3..67cb841da 100644
--- a/src/views/preview/meta.jsx
+++ b/src/views/preview/meta.jsx
@@ -4,7 +4,7 @@ const Helmet = require('react-helmet').default;
const projectShape = require('./projectshape.jsx').projectShape;
const Meta = props => {
- const {title, instructions, author} = props.projectInfo;
+ const {id, title, instructions, author} = props.projectInfo;
// Do not want to render any meta tags unless all the info is loaded
// Check only author (object) because it is ok to have empty string instructions
@@ -29,6 +29,10 @@ const Meta = props => {
content={truncatedInstructions}
property="og:description"
/>
+
);
};
From bbbe0f8836ecd89b5c6f791f3c122a37b3d3cdef Mon Sep 17 00:00:00 2001
From: Paul Kaplan
Date: Fri, 20 Sep 2019 13:48:08 -0400
Subject: [PATCH 06/38] Fix linter issues with prop ordering
---
src/views/preview/meta.jsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/views/preview/meta.jsx b/src/views/preview/meta.jsx
index 67cb841da..d526515a1 100644
--- a/src/views/preview/meta.jsx
+++ b/src/views/preview/meta.jsx
@@ -30,8 +30,8 @@ const Meta = props => {
property="og:description"
/>
);
From 0e7ed0e0fd4b99857d2a400df8b39233c0cd4a0d Mon Sep 17 00:00:00 2001
From: Ben Wheeler
Date: Fri, 20 Sep 2019 17:17:38 -0400
Subject: [PATCH 07/38] show info button in a way that does not increase line
height
---
src/components/info-button/info-button.scss | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/components/info-button/info-button.scss b/src/components/info-button/info-button.scss
index b776ee6a3..f2d93303d 100644
--- a/src/components/info-button/info-button.scss
+++ b/src/components/info-button/info-button.scss
@@ -7,6 +7,7 @@
width: 1rem;
height: 1rem;
margin-left: .375rem;
+ margin-top: -.25rem;
border-radius: 50%;
background-color: $type-gray-60percent;
background-image: url("/svgs/info-button/info-button.svg");
From 6bcde6129b896fb9e0a42997913fc0bac84cc070 Mon Sep 17 00:00:00 2001
From: Ben Wheeler
Date: Fri, 20 Sep 2019 17:17:53 -0400
Subject: [PATCH 08/38] shorter description line height
---
src/components/join-flow/join-flow-step.scss | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/join-flow/join-flow-step.scss b/src/components/join-flow/join-flow-step.scss
index 5c27e4253..e7de82007 100644
--- a/src/components/join-flow/join-flow-step.scss
+++ b/src/components/join-flow/join-flow-step.scss
@@ -27,7 +27,7 @@
.join-flow-description {
font-size: .875rem;
font-weight: bold;
- line-height: 1.37500rem;
+ line-height: 1.125rem;
margin-top: 0.78125rem;
margin-bottom: 1.875rem;
text-align: center;
From 106ccb34c691b1308cd02e7d001d622181142e6d Mon Sep 17 00:00:00 2001
From: Ben Wheeler
Date: Fri, 20 Sep 2019 17:18:26 -0400
Subject: [PATCH 09/38] make gender step height more flexible
---
src/components/join-flow/join-flow-steps.scss | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/src/components/join-flow/join-flow-steps.scss b/src/components/join-flow/join-flow-steps.scss
index abddb3b99..7488fd2b2 100644
--- a/src/components/join-flow/join-flow-steps.scss
+++ b/src/components/join-flow/join-flow-steps.scss
@@ -114,8 +114,6 @@
}
.join-flow-inner-gender-step {
- /* need height so that flex will adjust children proportionately */
- height: 27.25rem;
padding-top: 2.625rem;
padding-bottom: 1rem;
}
@@ -177,7 +175,7 @@
.gender-radio-row {
transition: all .125s ease;
width: 20.875rem;
- height: 2.85rem;
+ min-height: 2.85rem;
background-color: $ui-gray;
border-radius: .5rem;
margin: 0 auto 0.375rem;
From d905f624ca232df0b3a7c12a5c250fdf610101e4 Mon Sep 17 00:00:00 2001
From: Ben Wheeler
Date: Fri, 20 Sep 2019 18:22:15 -0400
Subject: [PATCH 10/38] gender step uses blank default, lcase values
---
src/components/join-flow/gender-step.jsx | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/components/join-flow/gender-step.jsx b/src/components/join-flow/gender-step.jsx
index 9148925ea..1aa358183 100644
--- a/src/components/join-flow/gender-step.jsx
+++ b/src/components/join-flow/gender-step.jsx
@@ -67,7 +67,7 @@ class GenderStep extends React.Component {
handleValidSubmit (formData, formikBag) {
formikBag.setSubmitting(false);
if (!formData.gender || formData.gender === 'null') {
- formData.gender = 'Prefer not to say';
+ formData.gender = ''; // default to blank
}
delete formData.custom;
this.props.onNextStep(formData);
@@ -102,20 +102,20 @@ class GenderStep extends React.Component {
id="GenderRadioOptionFemale"
label={this.props.intl.formatMessage({id: 'general.female'})}
selectedValue={values.gender}
- value="Female"
+ value="female"
onSetFieldValue={setFieldValue}
/>
Date: Sat, 21 Sep 2019 08:25:00 +0900
Subject: [PATCH 11/38] Update Code Club Link
---
src/views/teachers/landing/landing.jsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/views/teachers/landing/landing.jsx b/src/views/teachers/landing/landing.jsx
index 3b59db7a4..d4b0ac937 100644
--- a/src/views/teachers/landing/landing.jsx
+++ b/src/views/teachers/landing/landing.jsx
@@ -276,7 +276,7 @@ const Landing = props => (
id="teacherlanding.codeClub"
values={{
codeClubLink: (
-
+
)
From 693c8e11e3760317b121f02460df3d87e43e3204 Mon Sep 17 00:00:00 2001
From: Ben Wheeler
Date: Mon, 23 Sep 2019 10:36:58 -0400
Subject: [PATCH 12/38] new join flow modal stays open on click outside
---
src/components/modal/join/modal.jsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/components/modal/join/modal.jsx b/src/components/modal/join/modal.jsx
index d309433ee..287e6b31e 100644
--- a/src/components/modal/join/modal.jsx
+++ b/src/components/modal/join/modal.jsx
@@ -14,6 +14,7 @@ const JoinModal = ({
isOpen
useStandardSizes
className="mod-join"
+ shouldCloseOnOverlayClick={false}
onRequestClose={onRequestClose}
{...modalProps}
>
From 21e9494e1a96326390761c1b21a2807911c636d5 Mon Sep 17 00:00:00 2001
From: Eric Rosenbaum
Date: Mon, 23 Sep 2019 11:45:17 -0400
Subject: [PATCH 13/38] Update BOOST troubleshooting to suggest Link update
---
src/views/boost/boost.jsx | 4 ++--
src/views/boost/l10n.json | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/views/boost/boost.jsx b/src/views/boost/boost.jsx
index 1e3203ea6..d4abeac54 100644
--- a/src/views/boost/boost.jsx
+++ b/src/views/boost/boost.jsx
@@ -208,9 +208,9 @@ class Boost extends ExtensionLanding {
-
+
-
+
diff --git a/src/views/boost/l10n.json b/src/views/boost/l10n.json
index 470781f62..b99a7a056 100644
--- a/src/views/boost/l10n.json
+++ b/src/views/boost/l10n.json
@@ -12,8 +12,8 @@
"boost.connectALegoBeam": "Connect a LEGO beam with an axle to motor A and click the block again to make it spin.",
"boost.starterProjects": "Starter Projects",
"boost.troubleshootingTitle": "Troubleshooting",
- "boost.avoidFirmwareUpdateTitle": "Having trouble connecting or using motor blocks?",
- "boost.avoidFirmwareUpdateText": "We suggest you avoid updating the firmware on your BOOST for now. We are working on a fix with the team at LEGO for users who have already updated their firmware. Stay tuned! We hope to have a fix released soon.",
+ "boost.updateScratchLinkTitle": "Make sure you have the latest version of Scratch Link",
+ "boost.updateScratchLinkText": "Install Scratch Link using the button above. We recommend using the app store installation process to help keep your version up to date.",
"boost.checkOSVersionTitle": "Make sure your operating system is compatible with Scratch Link",
"boost.checkOSVersionText": "The minimum operating system versions are listed at the top of this page. See instructions for checking your version of {winOSVersionLink} or {macOSVersionLink}.",
"boost.winOSVersionLinkText": "Windows",
From 865e0a09dd18f576f5b8b3fa37509c4909b86940 Mon Sep 17 00:00:00 2001
From: Ben Wheeler
Date: Mon, 23 Sep 2019 13:23:38 -0400
Subject: [PATCH 14/38] give join-flow validations more spacing from inputs
they refer to; unset width of Required message
---
src/components/join-flow/join-flow-steps.scss | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
diff --git a/src/components/join-flow/join-flow-steps.scss b/src/components/join-flow/join-flow-steps.scss
index abddb3b99..ad5f05b9f 100644
--- a/src/components/join-flow/join-flow-steps.scss
+++ b/src/components/join-flow/join-flow-steps.scss
@@ -39,17 +39,15 @@
}
.validation-full-width-input {
- transform: translate(21.5625rem, 0);
+ transform: translate(21.8125rem, 0);
}
.validation-birthdate-month {
- transform: translate(-9.25rem, 0);
- width: 7.25rem;
+ transform: translate(-9.875rem, 0);
}
.validation-birthdate-year {
- transform: translate(8.75rem, 0);
- width: 7.25rem;
+ transform: translate(9.375rem, 0);
}
@media #{$intermediate-and-smaller} {
From d4c791adfb2fd3a0ded9c02a074976f403ae53d6 Mon Sep 17 00:00:00 2001
From: Ben Wheeler
Date: Mon, 23 Sep 2019 15:16:20 -0400
Subject: [PATCH 15/38] set modal close icon to draggable false
---
src/components/modal/base/modal.jsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/components/modal/base/modal.jsx b/src/components/modal/base/modal.jsx
index 156d435ce..7353b9080 100644
--- a/src/components/modal/base/modal.jsx
+++ b/src/components/modal/base/modal.jsx
@@ -56,6 +56,7 @@ class Modal extends React.Component {
From e901deb3981aeeb313155fa9b1c5224bba762a44 Mon Sep 17 00:00:00 2001
From: Ben Wheeler
Date: Mon, 23 Sep 2019 16:18:20 -0400
Subject: [PATCH 16/38] =?UTF-8?q?make=20validations=20be=20the=20same=20wi?=
=?UTF-8?q?dth=20as=20inputs=E2=80=A6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
…when window is narrow
---
src/components/join-flow/birthdate-step.jsx | 6 +++++-
src/components/join-flow/country-step.jsx | 5 ++++-
src/components/join-flow/join-flow-steps.scss | 18 ++++++++++++++++--
3 files changed, 25 insertions(+), 4 deletions(-)
diff --git a/src/components/join-flow/birthdate-step.jsx b/src/components/join-flow/birthdate-step.jsx
index 6849bc126..7e507468b 100644
--- a/src/components/join-flow/birthdate-step.jsx
+++ b/src/components/join-flow/birthdate-step.jsx
@@ -117,6 +117,7 @@ class BirthDateStep extends React.Component {
options={birthMonthOptions}
validate={this.validateSelect}
validationClassName={classNames(
+ 'validation-birthdate',
'validation-birthdate-month',
'validation-left'
)}
@@ -135,7 +136,10 @@ class BirthDateStep extends React.Component {
name="birth_year"
options={birthYearOptions}
validate={this.validateSelect}
- validationClassName="validation-birthdate-year"
+ validationClassName={classNames(
+ 'validation-birthdate',
+ 'validation-birthdate-year'
+ )}
/* eslint-disable react/jsx-no-bind */
onFocus={() => setFieldError('birth_year', null)}
/* eslint-enable react/jsx-no-bind */
diff --git a/src/components/join-flow/country-step.jsx b/src/components/join-flow/country-step.jsx
index 1e6ebd252..92ca4559c 100644
--- a/src/components/join-flow/country-step.jsx
+++ b/src/components/join-flow/country-step.jsx
@@ -93,7 +93,10 @@ class CountryStep extends React.Component {
name="country"
options={this.countryOptions}
validate={this.validateSelect}
- validationClassName="validation-full-width-input"
+ validationClassName={classNames(
+ 'validation-full-width-input',
+ 'validation-country'
+ )}
/* eslint-disable react/jsx-no-bind */
onFocus={() => setFieldError('country', null)}
/* eslint-enable react/jsx-no-bind */
diff --git a/src/components/join-flow/join-flow-steps.scss b/src/components/join-flow/join-flow-steps.scss
index 7488fd2b2..ebfcb73f2 100644
--- a/src/components/join-flow/join-flow-steps.scss
+++ b/src/components/join-flow/join-flow-steps.scss
@@ -56,11 +56,25 @@
.validation-full-width-input {
transform: unset;
margin-bottom: .75rem;
+ max-width: 100%;
}
- .validation-birthdate-input {
+ .validation-country {
+ top: .5rem;
+ }
+
+ .validation-birthdate {
transform: unset;
- width: 8rem;
+ top: .5rem;
+ width: 19rem;
+ }
+
+ .validation-birthdate-month {
+ margin-right: -9.25rem;
+ }
+
+ .validation-birthdate-year {
+ margin-left: -9.625rem;
}
}
From 0d60b64c392b4aca469210e491084bf477497551 Mon Sep 17 00:00:00 2001
From: picklesrus
Date: Fri, 20 Sep 2019 14:40:49 -0400
Subject: [PATCH 17/38] Add a prop to Modal that allows you to hide the close
button. Set it to show by default and have the standalone join flow page set
it to hidden.
---
src/components/modal/base/modal.jsx | 29 ++++++++++++++----------
src/components/modal/join/modal.jsx | 4 +++-
src/views/join/join.jsx | 2 ++
test/unit/components/modal.test.jsx | 34 +++++++++++++++++++++++++++++
4 files changed, 57 insertions(+), 12 deletions(-)
create mode 100644 test/unit/components/modal.test.jsx
diff --git a/src/components/modal/base/modal.jsx b/src/components/modal/base/modal.jsx
index 7353b9080..0203a0f41 100644
--- a/src/components/modal/base/modal.jsx
+++ b/src/components/modal/base/modal.jsx
@@ -49,17 +49,20 @@ class Modal extends React.Component {
}}
{...omit(this.props, ['className', 'overlayClassName'])}
>
-
-
-
+
+ {this.props.showCloseButton && (
+
+
+
+ )}
{this.props.children}
);
@@ -70,7 +73,11 @@ Modal.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
overlayClassName: PropTypes.string,
+ showCloseButton: PropTypes.bool,
useStandardSizes: PropTypes.bool
};
+Modal.defaultProps = {
+ showCloseButton: true
+};
module.exports = Modal;
diff --git a/src/components/modal/join/modal.jsx b/src/components/modal/join/modal.jsx
index 287e6b31e..bd17ac6ab 100644
--- a/src/components/modal/join/modal.jsx
+++ b/src/components/modal/join/modal.jsx
@@ -12,6 +12,7 @@ const JoinModal = ({
}) => (
(
);
diff --git a/test/unit/components/modal.test.jsx b/test/unit/components/modal.test.jsx
new file mode 100644
index 000000000..6c427dc2f
--- /dev/null
+++ b/test/unit/components/modal.test.jsx
@@ -0,0 +1,34 @@
+const React = require('react');
+const {shallowWithIntl} = require('../../helpers/intl-helpers.jsx');
+const Modal = require('../../../src/components/modal/base/modal.jsx');
+
+describe('Modal', () => {
+ test('Close button not shown when showCloseButton false', () => {
+ const showClose = true;
+ const component = shallowWithIntl(
+
+ );
+ expect(component.find('div.modal-content-close').exists()).toBe(true);
+ expect(component.find('img.modal-content-close-img').exists()).toBe(true);
+ });
+ test('Close button shown by default', () => {
+ const component = shallowWithIntl(
+
+ );
+ expect(component.find('div.modal-content-close').exists()).toBe(true);
+ expect(component.find('img.modal-content-close-img').exists()).toBe(true);
+ });
+
+ test('Close button shown when showCloseButton true', () => {
+ const showClose = false;
+ const component = shallowWithIntl(
+
+ );
+ expect(component.find('div.modal-content-close').exists()).toBe(false);
+ expect(component.find('img.modal-content-close-img').exists()).toBe(false);
+ });
+});
From 1a580957821e21c9e6199b8b4e1543f64094ffb5 Mon Sep 17 00:00:00 2001
From: picklesrus
Date: Mon, 23 Sep 2019 16:15:20 -0400
Subject: [PATCH 18/38] Put values directly in render props.
---
src/views/join/join.jsx | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/src/views/join/join.jsx b/src/views/join/join.jsx
index 3064ec08b..60379fd86 100644
--- a/src/views/join/join.jsx
+++ b/src/views/join/join.jsx
@@ -5,14 +5,12 @@ const ErrorBoundary = require('../../components/errorboundary/errorboundary.jsx'
// Require this even though we don't use it because, without it, webpack runs out of memory...
const Page = require('../../components/page/www/page.jsx'); // eslint-disable-line no-unused-vars
-const openModal = true;
-const showCloseButton = false;
const Register = () => (
);
From fd131b84c15f5410cd7ca3f059fd725d45c5ff6d Mon Sep 17 00:00:00 2001
From: picklesrus
Date: Mon, 23 Sep 2019 18:04:59 -0400
Subject: [PATCH 19/38] Add Logo to join flow standalone page. Will need some
css work, especially for small screens.
---
src/views/join/join.jsx | 13 +++++++++++++
src/views/join/join.scss | 29 +++++++++++++++++++++++++++++
2 files changed, 42 insertions(+)
create mode 100644 src/views/join/join.scss
diff --git a/src/views/join/join.jsx b/src/views/join/join.jsx
index 60379fd86..a62e7d449 100644
--- a/src/views/join/join.jsx
+++ b/src/views/join/join.jsx
@@ -5,8 +5,21 @@ const ErrorBoundary = require('../../components/errorboundary/errorboundary.jsx'
// Require this even though we don't use it because, without it, webpack runs out of memory...
const Page = require('../../components/page/www/page.jsx'); // eslint-disable-line no-unused-vars
+require('./join.scss');
const Register = () => (
+
Date: Tue, 24 Sep 2019 14:34:02 -0400
Subject: [PATCH 20/38] Use a feature flag to contain new Scratch Desktop
updates
---
.../extension-landing/extension-landing.jsx | 12 +-
.../install-scratch-link.jsx | 9 +-
.../install-scratch/install-scratch.jsx | 146 +++++++++
.../install-scratch/install-scratch.scss | 95 ++++++
.../install-scratch/install-util.js | 13 +
src/components/os-chooser/os-chooser.jsx | 25 +-
src/l10n.json | 10 +-
src/lib/detect-os.js | 18 ++
src/lib/feature-flags.js | 10 +
.../extension-landing => lib}/os-enum.js | 4 +-
src/views/download/download.jsx | 299 ++++++++++--------
src/views/download/download.scss | 13 +
src/views/download/l10n.json | 19 +-
src/views/ev3/ev3.jsx | 2 +-
src/views/microbit/microbit.jsx | 2 +-
static/images/badges/google-play-badge.png | Bin 0 -> 16087 bytes
static/images/badges/mac-store-badge.svg | 46 +++
static/images/badges/windows-store-badge.svg | 82 +++++
static/svgs/download/mac-badge.svg | 46 +++
static/svgs/download/ms-badge.svg | 82 +++++
20 files changed, 768 insertions(+), 165 deletions(-)
create mode 100644 src/components/install-scratch/install-scratch.jsx
create mode 100644 src/components/install-scratch/install-scratch.scss
create mode 100644 src/components/install-scratch/install-util.js
create mode 100644 src/lib/detect-os.js
create mode 100644 src/lib/feature-flags.js
rename src/{components/extension-landing => lib}/os-enum.js (51%)
create mode 100644 static/images/badges/google-play-badge.png
create mode 100755 static/images/badges/mac-store-badge.svg
create mode 100755 static/images/badges/windows-store-badge.svg
create mode 100755 static/svgs/download/mac-badge.svg
create mode 100755 static/svgs/download/ms-badge.svg
diff --git a/src/components/extension-landing/extension-landing.jsx b/src/components/extension-landing/extension-landing.jsx
index 44cb3fb01..c883d294f 100644
--- a/src/components/extension-landing/extension-landing.jsx
+++ b/src/components/extension-landing/extension-landing.jsx
@@ -1,7 +1,7 @@
const bindAll = require('lodash.bindall');
const React = require('react');
-const OS_ENUM = require('./os-enum.js');
+const detectOS = require('../../lib/detect-os.js').default;
class ExtensionLanding extends React.Component {
constructor (props) {
@@ -10,16 +10,8 @@ class ExtensionLanding extends React.Component {
'onSetOS'
]);
- // @todo use bowser for browser detection
- let detectedOS = OS_ENUM.WINDOWS;
- if (window.navigator && window.navigator.platform) {
- if (window.navigator.platform === 'MacIntel') {
- detectedOS = OS_ENUM.MACOS;
- }
- }
-
this.state = {
- OS: detectedOS
+ OS: detectOS()
};
}
diff --git a/src/components/extension-landing/install-scratch-link.jsx b/src/components/extension-landing/install-scratch-link.jsx
index b8186fbcf..507cdbeb7 100644
--- a/src/components/extension-landing/install-scratch-link.jsx
+++ b/src/components/extension-landing/install-scratch-link.jsx
@@ -2,13 +2,14 @@ const PropTypes = require('prop-types');
const FormattedMessage = require('react-intl').FormattedMessage;
const React = require('react');
-const OS_ENUM = require('./os-enum.js');
+const OS_ENUM = require('../../lib/os-enum.js');
const FlexRow = require('../../components/flex-row/flex-row.jsx');
const Steps = require('../../components/steps/steps.jsx');
const Step = require('../../components/steps/step.jsx');
require('./extension-landing.scss');
+// Assumes this will only be called with an OS that needs Scratch Link
const InstallScratchLink = ({
currentOS
}) => (
@@ -37,20 +38,20 @@ const InstallScratchLink = ({
-
+
-
+
diff --git a/src/components/install-scratch/install-scratch.jsx b/src/components/install-scratch/install-scratch.jsx
new file mode 100644
index 000000000..eff760cff
--- /dev/null
+++ b/src/components/install-scratch/install-scratch.jsx
@@ -0,0 +1,146 @@
+const PropTypes = require('prop-types');
+const FormattedMessage = require('react-intl').FormattedMessage;
+const React = require('react');
+
+const OS_ENUM = require('../../lib/os-enum.js');
+const {CHROME_APP_RELEASED} = require('../../lib/feature-flags.js');
+
+const {isDownloaded, isFromGooglePlay} = require('./install-util.js');
+
+const FlexRow = require('../../components/flex-row/flex-row.jsx');
+const Steps = require('../../components/steps/steps.jsx');
+const Step = require('../../components/steps/step.jsx');
+
+require('./install-scratch.scss');
+
+const InstallScratch = ({
+ currentOS
+}) => (
+
+
+
+ {CHROME_APP_RELEASED ? (
+
+ ) : (
+
+ {isDownloaded(currentOS) && (
+
+ )}
+ {isFromGooglePlay(currentOS) && (
+
+ )}
+
+ )}
+
+
+
+
+
+
+ {isDownloaded(currentOS) && (
+
+ )}
+ {isFromGooglePlay(currentOS) && (
+
+ )}
+
+
+
+ {currentOS === OS_ENUM.WINDOWS && (
+
+
+
+ )}
+ {currentOS === OS_ENUM.MACOS && (
+
+
+
+ )}
+ {isFromGooglePlay(currentOS) && (
+
+
+
+
+ )}
+ {isDownloaded(currentOS) && (
+
+
+
+
+
+
+
+
+ )}
+
+
+
+
+ {isDownloaded(currentOS) && (
+
+
+ {currentOS === OS_ENUM.WINDOWS ?
+ :
+
+ }
+
+
+
+
+
+
+ )}
+
+
+
+);
+
+InstallScratch.propTypes = {
+ currentOS: PropTypes.string
+};
+
+module.exports = InstallScratch;
diff --git a/src/components/install-scratch/install-scratch.scss b/src/components/install-scratch/install-scratch.scss
new file mode 100644
index 000000000..953b93d5a
--- /dev/null
+++ b/src/components/install-scratch/install-scratch.scss
@@ -0,0 +1,95 @@
+@import "../../colors";
+@import "../../frameless";
+
+#view {
+ padding: 0;
+}
+
+.install-scratch {
+ padding: 2rem 0;
+
+ .inner {
+ align-items: flex-start;
+ }
+
+ .downloads-container {
+ text-align: center;
+
+ .horizontal-divider {
+ display: block;
+ margin: 20px;
+ }
+
+ .horizontal-divider:before,
+ .horizontal-divider:after {
+ display: inline-block;
+ position: relative;
+ background-color: $ui-dark-gray;
+ width: 50%;
+ height: 1px;
+ vertical-align: middle;
+ content: "";
+ }
+
+ .horizontal-divider:before {
+ right: .5em;
+ margin-left: -50%;
+ }
+
+ .horizontal-divider:after {
+ left: .5em;
+ margin-right: -50%;
+ }
+ }
+
+ .step-image {
+ height: 14rem;
+ }
+
+ .title {
+ margin-bottom: 2rem;
+ font-size: 2rem;
+ }
+
+ .legacy-link {
+ display: flex;
+ }
+
+ .download-button {
+ display: inline-block;
+ margin: .5em 0;
+ border: 0;
+ border-radius: 8px;
+ background-color: $ui-blue;
+ cursor: pointer;
+ padding: 1rem 2rem;
+ color: $ui-white;
+ font-size: 1rem;
+ }
+
+ .macos-badge img {
+ height: 50px;
+ }
+
+ .ms-badge img {
+ height: 50px;
+ }
+
+ .play-badge img {
+ height: 50px;
+ }
+
+ .download-image {
+ width: 100%;
+ max-width: $cols6;
+
+ img {
+ max-width: 100%;
+ max-height: 100%;
+ }
+ }
+
+ .blue {
+ background-color: $ui-blue-10percent;
+ }
+}
diff --git a/src/components/install-scratch/install-util.js b/src/components/install-scratch/install-util.js
new file mode 100644
index 000000000..20f667c21
--- /dev/null
+++ b/src/components/install-scratch/install-util.js
@@ -0,0 +1,13 @@
+const OS_ENUM = require('../../lib/os-enum.js');
+
+module.exports = {};
+
+module.exports.isDownloaded = os => {
+ if (os === OS_ENUM.WINDOWS || os === OS_ENUM.MACOS) return true;
+ return false;
+};
+
+module.exports.isFromGooglePlay = os => {
+ if (os === OS_ENUM.ANDROID || os === OS_ENUM.CHROMEOS) return true;
+ return false;
+};
diff --git a/src/components/os-chooser/os-chooser.jsx b/src/components/os-chooser/os-chooser.jsx
index 977c207ea..5d77d19e2 100644
--- a/src/components/os-chooser/os-chooser.jsx
+++ b/src/components/os-chooser/os-chooser.jsx
@@ -4,11 +4,12 @@ const FormattedMessage = require('react-intl').FormattedMessage;
const PropTypes = require('prop-types');
const React = require('react');
+const {CHROME_APP_RELEASED} = require('../../lib/feature-flags.js');
const FlexRow = require('../../components/flex-row/flex-row.jsx');
const Button = require('../../components/forms/button.jsx');
-const OS_ENUM = require('../../components/extension-landing/os-enum.js');
+const OS_ENUM = require('../../lib/os-enum.js');
require('./os-chooser.scss');
@@ -34,6 +35,28 @@ const OSChooser = props => (
macOS
+ {CHROME_APP_RELEASED && (
+
+
+
+
+ )}
);
diff --git a/src/l10n.json b/src/l10n.json
index 9b9032b7a..a72a5de67 100644
--- a/src/l10n.json
+++ b/src/l10n.json
@@ -133,10 +133,16 @@
"oschooser.choose": "Choose your OS:",
+ "installScratch.or": "or",
+ "installScratch.directDownload": "Direct download",
+ "installScratch.desktopHeaderTitle": "Install Scratch Desktop",
+ "installScratch.appHeaderTitle": "Install Scratch for {operatingsystem}",
+ "installScratch.downloadScratchDesktop": "Download Scratch Desktop",
+ "installScratch.downloadScratchAppGeneric": "Download Scratch for {operatingsystem}",
+ "installScratch.getScratchAppPlay": "Get Scratch on the Google Play Store",
+
"installScratchLink.installHeaderTitle": "Install Scratch Link",
"installScratchLink.downloadAndInstall": "Download and install Scratch Link.",
- "installScratchLink.or": "or",
- "installScratchLink.directDownload": "Direct download",
"installScratchLink.startScratchLink": "Start Scratch Link and make sure it is running. It should appear in your toolbar.",
"parents.FaqAgeRangeA": "While Scratch is primarily designed for 8 to 16 year olds, it is also used by people of all ages, including younger children with their parents.",
diff --git a/src/lib/detect-os.js b/src/lib/detect-os.js
new file mode 100644
index 000000000..fa94c3229
--- /dev/null
+++ b/src/lib/detect-os.js
@@ -0,0 +1,18 @@
+import bowser from 'bowser';
+import OS_ENUM from './os-enum.js';
+import {CHROME_APP_RELEASED} from './feature-flags.js';
+
+/**
+ * Helper function to the current Operating System.
+ * @returns {OS_ENUM} Returns the OS value, defaults to WINDOWS
+ */
+export default function () {
+ // matching OS strings from https://github.com/lancedikson/bowser/blob/master/src/constants.js
+ if (bowser.osname === 'macOS') return OS_ENUM.MACOS;
+ if (CHROME_APP_RELEASED) {
+ if (bowser.osname === 'Chrome OS') return OS_ENUM.CHROMEOS;
+ if (bowser.osname === 'Android') return OS_ENUM.ANDROID;
+ }
+ // if (bowser.osname === 'iOS') return OS_ENUM.IOS; // @todo
+ return OS_ENUM.WINDOWS;
+}
diff --git a/src/lib/feature-flags.js b/src/lib/feature-flags.js
new file mode 100644
index 000000000..2965c1e9c
--- /dev/null
+++ b/src/lib/feature-flags.js
@@ -0,0 +1,10 @@
+const isStaging = () => process.env.SCRATCH_ENV === 'staging';
+
+const flagInUrl = flag => {
+ const url = (window.location && window.location.search) || '';
+ return url.indexOf(`${flag}=true`) !== -1;
+};
+
+module.exports = {
+ CHROME_APP_RELEASED: isStaging() && flagInUrl('CHROME_APP_RELEASED')
+};
diff --git a/src/components/extension-landing/os-enum.js b/src/lib/os-enum.js
similarity index 51%
rename from src/components/extension-landing/os-enum.js
rename to src/lib/os-enum.js
index b8cde494e..24d41c018 100644
--- a/src/components/extension-landing/os-enum.js
+++ b/src/lib/os-enum.js
@@ -1,6 +1,8 @@
const OS_ENUM = {
WINDOWS: 'Windows',
- MACOS: 'macOS'
+ MACOS: 'macOS',
+ CHROMEOS: 'ChromeOS',
+ ANDROID: 'Android'
};
module.exports = OS_ENUM;
diff --git a/src/views/download/download.jsx b/src/views/download/download.jsx
index 2c935f04b..b0f77a4a8 100644
--- a/src/views/download/download.jsx
+++ b/src/views/download/download.jsx
@@ -5,13 +5,14 @@ const React = require('react');
const FlexRow = require('../../components/flex-row/flex-row.jsx');
const bindAll = require('lodash.bindall');
-const Steps = require('../../components/steps/steps.jsx');
-const Step = require('../../components/steps/step.jsx');
const Page = require('../../components/page/www/page.jsx');
const render = require('../../lib/render.jsx');
-const OS_ENUM = require('../../components/extension-landing/os-enum.js');
+const detectOS = require('../../lib/detect-os.js').default;
+const {CHROME_APP_RELEASED} = require('../../lib/feature-flags.js');
const OSChooser = require('../../components/os-chooser/os-chooser.jsx');
+const InstallScratch = require('../../components/install-scratch/install-scratch.jsx');
+const {isDownloaded, isFromGooglePlay} = require('../../components/install-scratch/install-util.js');
require('./download.scss');
require('../../components/forms/button.scss');
@@ -22,15 +23,9 @@ class Download extends React.Component {
bindAll(this, [
'onSetOS'
]);
- let detectedOS = OS_ENUM.WINDOWS;
- if (window.navigator && window.navigator.platform) {
- if (window.navigator.platform === 'MacIntel') {
- detectedOS = OS_ENUM.MACOS;
- }
- }
this.state = {
- OS: detectedOS
+ OS: detectOS()
};
}
@@ -55,10 +50,16 @@ class Download extends React.Component {
src="/images/download/icon.png"
width="40"
/>
-
+
-
+
@@ -80,6 +81,24 @@ class Download extends React.Component {
/>
macOS 10.13+
+ {CHROME_APP_RELEASED && (
+
+
+
+ ChromeOS
+
+
+
+ Android 5.0+
+
+
+ )}
@@ -95,89 +114,150 @@ class Download extends React.Component {
currentOS={this.state.OS}
handleSetOS={this.onSetOS}
/>
-
-
-
-
-
-
-
-
-
-
-
-
+
+ {isDownloaded(this.state.OS) && (
+
-
-
- {this.state.OS === OS_ENUM.WINDOWS ?
- :
-
- }
-
-
-
-
+
-
-
-
-
+
+
+
+
+ )}
+ {isDownloaded(this.state.OS) && (
+
+
+
+
+
+
+
+
+ )}
+ {isFromGooglePlay(this.state.OS) && (
+
+
+
+
+
+
+
+
+ )}
-
-
-
-
-
-
-
+ {isFromGooglePlay(this.state.OS) ?
+ :
+
+ }
-
-
-
-
-
-
-
-
-
-
-
-
+ {isDownloaded(this.state.OS) && (CHROME_APP_RELEASED ? (
+
+
+
+
+
+
+
+
+ ) : (
+
+
+
+
+
+
+
+
+ ))}
+ {isFromGooglePlay(this.state.OS) && (
+
+
+
+
+
+
+
+
+ )}
+ {!CHROME_APP_RELEASED && (
+
+
+
+
+
+
+
+
+ )}
@@ -186,64 +266,7 @@ class Download extends React.Component {
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
);
diff --git a/src/views/download/download.scss b/src/views/download/download.scss
index e53275950..3e2b4064c 100644
--- a/src/views/download/download.scss
+++ b/src/views/download/download.scss
@@ -27,6 +27,18 @@
font-size: 1rem;
}
+ .macos-badge img {
+ height: 50px;
+ }
+
+ .ms-badge img {
+ height: 50px;
+ }
+
+ .play-badge img {
+ height: 50px;
+ }
+
.download-header {
background-color: $ui-blue;
padding: 4rem 0;
@@ -80,6 +92,7 @@
.download-requirements {
justify-content: space-between;
+ line-height: 2rem;
}
.download-requirements span {
diff --git a/src/views/download/l10n.json b/src/views/download/l10n.json
index e475c8a75..c1559b070 100644
--- a/src/views/download/l10n.json
+++ b/src/views/download/l10n.json
@@ -1,11 +1,10 @@
{
"download.title": "Scratch Desktop",
"download.intro": "You can install the Scratch Desktop editor to work on projects without an internet connection. This version will work on Windows and MacOS.",
+ "download.appTitle": "Download Scratch",
+ "download.appIntro": "You can install Scratch for free to work on projects without an internet connection.",
"download.requirements": "Requirements",
"download.imgAltDownloadIllustration" : "Scratch 3.0 Desktop screenshot",
- "download.installHeaderTitle": "Install Scratch Desktop",
- "download.downloadScratchDesktop": "Download Scratch Desktop",
- "download.downloadButton": "Download",
"download.troubleshootingTitle": "FAQ",
"download.startScratchDesktop": "Start Scratch Desktop",
"download.howDoIInstall": "How do I install Scratch Desktop?",
@@ -14,17 +13,23 @@
"download.supportChromeOS" : "When will you have Scratch Desktop for Chromebooks?",
"download.supportChromeOSAnswer": "Scratch Desktop for Chromebooks is not yet available. We are working on it and expect to release later in 2019.",
"download.olderVersionsTitle" : "Older Versions",
- "download.olderVersions": "Looking for the Scratch 2.0 Offline Editor or Scratch 1.4?",
- "download.scratch1-4Desktop" : "Scratch 1.4 Desktop",
- "download.scratch2Desktop" : "Scratch 2.0 Desktop",
+ "download.olderVersions": "Looking for earlier Scratch Offline Editors?",
+ "download.scratch1-4Desktop" : "Scratch 1.4",
+ "download.scratch2Desktop" : "Scratch 2.0 Offline Editor",
"download.cannotAccessMacStore" : "What if I can't access the Mac App Store?",
"download.cannotAccessWindowsStore" : "What if I can't access the Microsoft Store?",
"download.macMoveToApplications" : "Open the .dmg file. Move Scratch Desktop into Applications.",
"download.winMoveToApplications" : "Run the .exe file.",
"download.canIUseScratchLink" : "Can I use Scratch Link to connect to extensions?",
"download.canIUseScratchLinkAnswer" : "Yes. However, you will need an Internet connection to use Scratch Link.",
+ "download.canIUseExtensions" : "Can I connect to hardware extensions?",
+ "download.canIUseExtensionsAnswer" : "Yes. With the Scratch app you can connect to extensions, and you do not need Scratch Link.",
"download.desktopAndBrowser": "Can I use Scratch Desktop and also have Scratch open in the browser?",
+ "download.appAndBrowser": "Can I use the Scratch app and also have Scratch open in the browser?",
"download.yesAnswer" : "Yes.",
"download.canIShare": "Can I share from Scratch Desktop?",
- "download.canIShareAnswer": "This isn’t supported currently. For now, you can save a project from Scratch Desktop, upload it to your Scratch account, and share it there. In a later version we will add the ability to upload to your Scratch account directly in Scratch Desktop."
+ "download.canIShareAnswer": "This isn’t supported currently. For now, you can save a project from Scratch Desktop, upload it to your Scratch account, and share it there. In a later version we will add the ability to upload to your Scratch account directly in Scratch Desktop.",
+ "download.canIShareApp": "Can I share from Scratch for {operatingsystem}?",
+ "download.canIShareAnswerPlayStore": "Yes. Click the 3-dots menu on a project in the lobby and select Share from the options. In addition to sharing by email you can sign in to your Scratch account and share the project on the Scratch Community.",
+ "download.canIShareAnswerDownloaded": "This isn’t supported currently. For now, you can save a project from Scratch for {operatingsystem}, upload it to your Scratch account, and share it there. In a later version we will add the ability to upload to your Scratch account directly in Scratch for {operatingsystem}."
}
diff --git a/src/views/ev3/ev3.jsx b/src/views/ev3/ev3.jsx
index a03b55a14..6666490e9 100644
--- a/src/views/ev3/ev3.jsx
+++ b/src/views/ev3/ev3.jsx
@@ -23,7 +23,7 @@ const ProjectCard = require('../../components/extension-landing/project-card.jsx
const Steps = require('../../components/steps/steps.jsx');
const Step = require('../../components/steps/step.jsx');
-const OS_ENUM = require('../../components/extension-landing/os-enum.js');
+const OS_ENUM = require('../../lib/os-enum.js');
require('../../components/extension-landing/extension-landing.scss');
require('./ev3.scss');
diff --git a/src/views/microbit/microbit.jsx b/src/views/microbit/microbit.jsx
index 1b36c4ba3..ac274423b 100644
--- a/src/views/microbit/microbit.jsx
+++ b/src/views/microbit/microbit.jsx
@@ -22,7 +22,7 @@ const Button = require('../../components/forms/button.jsx');
const Steps = require('../../components/steps/steps.jsx');
const Step = require('../../components/steps/step.jsx');
-const OS_ENUM = require('../../components/extension-landing/os-enum.js');
+const OS_ENUM = require('../../lib/os-enum.js');
require('../../components/extension-landing/extension-landing.scss');
require('./microbit.scss');
diff --git a/static/images/badges/google-play-badge.png b/static/images/badges/google-play-badge.png
new file mode 100644
index 0000000000000000000000000000000000000000..27392d21753e7e06361a69aa19cbb771bbbb288e
GIT binary patch
literal 16087
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
fsQ>tuapn0PWb2aei^0>sch99I
+ Download_on_the_App_Store_Badge_US-UK_RGB_blk_4SVG_092917
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/static/images/badges/windows-store-badge.svg b/static/images/badges/windows-store-badge.svg
new file mode 100755
index 000000000..21c139edd
--- /dev/null
+++ b/static/images/badges/windows-store-badge.svg
@@ -0,0 +1,82 @@
+
+
+
diff --git a/static/svgs/download/mac-badge.svg b/static/svgs/download/mac-badge.svg
new file mode 100755
index 000000000..072b425a1
--- /dev/null
+++ b/static/svgs/download/mac-badge.svg
@@ -0,0 +1,46 @@
+
diff --git a/static/svgs/download/ms-badge.svg b/static/svgs/download/ms-badge.svg
new file mode 100755
index 000000000..21c139edd
--- /dev/null
+++ b/static/svgs/download/ms-badge.svg
@@ -0,0 +1,82 @@
+
+
+
From 762dc324b8e6589ad0fdd7d707e834d51ec53d69 Mon Sep 17 00:00:00 2001
From: Paul Kaplan
Date: Tue, 24 Sep 2019 15:15:22 -0400
Subject: [PATCH 21/38] Update extension pages for new OS picker
---
.../extension-requirements.jsx | 55 +++-
src/l10n.json | 3 +-
src/views/boost/boost.jsx | 143 +++++-----
src/views/ev3/ev3.jsx | 198 +++++++-------
src/views/ev3/ev3.scss | 3 +
src/views/ev3/l10n.json | 2 +
src/views/gdxfor/gdxfor.jsx | 135 +++++-----
src/views/microbit/l10n.json | 1 +
src/views/microbit/microbit.jsx | 245 ++++++++++--------
src/views/wedo2/wedo2.jsx | 135 +++++-----
static/images/ev3/chromeos-enter-passcode.png | Bin 0 -> 32951 bytes
static/images/microbit/chromeos-copy-hex.png | Bin 0 -> 54352 bytes
static/images/scratchlink/mac-store-badge.svg | 46 ----
.../scratchlink/windows-store-badge.svg | 82 ------
static/svgs/extensions/android.svg | 14 +
static/svgs/extensions/chromeos.svg | 14 +
16 files changed, 518 insertions(+), 558 deletions(-)
create mode 100644 static/images/ev3/chromeos-enter-passcode.png
create mode 100644 static/images/microbit/chromeos-copy-hex.png
delete mode 100755 static/images/scratchlink/mac-store-badge.svg
delete mode 100755 static/images/scratchlink/windows-store-badge.svg
create mode 100644 static/svgs/extensions/android.svg
create mode 100644 static/svgs/extensions/chromeos.svg
diff --git a/src/components/extension-landing/extension-requirements.jsx b/src/components/extension-landing/extension-requirements.jsx
index 67bc2ba46..3754eb02c 100644
--- a/src/components/extension-landing/extension-requirements.jsx
+++ b/src/components/extension-landing/extension-requirements.jsx
@@ -4,6 +4,8 @@ const React = require('react');
const FlexRow = require('../../components/flex-row/flex-row.jsx');
+const {CHROME_APP_RELEASED} = require('../../lib/feature-flags.js');
+
require('./extension-landing.scss');
const ExtensionRequirements = props => (
@@ -12,13 +14,64 @@ const ExtensionRequirements = props => (
- {props.children}
+ {props.bluetoothStandard ? (
+
+
+
+ Windows 10 version 1709+
+
+
+
+ macOS 10.13+
+
+ {CHROME_APP_RELEASED && (
+
+
+
+ ChromeOS
+
+
+
+ Android 5.0+
+
+
+ )}
+
+
+ Bluetooth
+
+
+
+ Scratch Link
+
+
+ ) : props.children}
);
ExtensionRequirements.propTypes = {
+ bluetoothStandard: PropTypes.bool,
children: PropTypes.node
};
+ExtensionRequirements.defaultProps = {
+ bluetoothStandard: false
+};
+
module.exports = ExtensionRequirements;
diff --git a/src/l10n.json b/src/l10n.json
index a72a5de67..3b5dbc393 100644
--- a/src/l10n.json
+++ b/src/l10n.json
@@ -140,7 +140,8 @@
"installScratch.downloadScratchDesktop": "Download Scratch Desktop",
"installScratch.downloadScratchAppGeneric": "Download Scratch for {operatingsystem}",
"installScratch.getScratchAppPlay": "Get Scratch on the Google Play Store",
-
+ "installScratch.useScratchApp": "Open the Scratch app on your device.",
+
"installScratchLink.installHeaderTitle": "Install Scratch Link",
"installScratchLink.downloadAndInstall": "Download and install Scratch Link.",
"installScratchLink.startScratchLink": "Start Scratch Link and make sure it is running. It should appear in your toolbar.",
diff --git a/src/views/boost/boost.jsx b/src/views/boost/boost.jsx
index d4abeac54..5c3b885eb 100644
--- a/src/views/boost/boost.jsx
+++ b/src/views/boost/boost.jsx
@@ -9,12 +9,14 @@ const render = require('../../lib/render.jsx');
const FlexRow = require('../../components/flex-row/flex-row.jsx');
const OSChooser = require('../../components/os-chooser/os-chooser.jsx');
+const {isDownloaded, isFromGooglePlay} = require('../../components/install-scratch/install-util.js');
const ExtensionLanding = require('../../components/extension-landing/extension-landing.jsx');
const ExtensionHeader = require('../../components/extension-landing/extension-header.jsx');
const ExtensionRequirements = require('../../components/extension-landing/extension-requirements.jsx');
const ExtensionSection = require('../../components/extension-landing/extension-section.jsx');
const InstallScratchLink = require('../../components/extension-landing/install-scratch-link.jsx');
+const InstallScratch = require('../../components/install-scratch/install-scratch.jsx');
const ProjectCard = require('../../components/extension-landing/project-card.jsx');
const Steps = require('../../components/steps/steps.jsx');
@@ -56,45 +58,23 @@ class Boost extends ExtensionLanding {
src="/images/boost/boost-header.svg"
/>}
renderRequirements={
-
-
-
- Windows 10 version 1709+
-
-
-
- macOS 10.13+
-
-
-
- Bluetooth 4.0
-
-
-
- Scratch Link
-
-
+
}
/>
-
+ {(isDownloaded(this.state.OS)) && (
+
+ )}
+ {(isFromGooglePlay(this.state.OS)) && (
+
+ )}
@@ -109,20 +89,25 @@ class Boost extends ExtensionLanding {
/>
-
- Scratch
-
- )
- }}
- />
+ {isDownloaded(this.state.OS) && (
+
+ Scratch
+
+ )
+ }}
+ />
+ )}
+ {isFromGooglePlay(this.state.OS) && (
+
+ )}
@@ -208,36 +193,40 @@ class Boost extends ExtensionLanding {
-
-
-
-
-
-
-
-
-
- ),
- macOSVersionLink: (
-
-
-
- )
- }}
- />
-
+ {isDownloaded(this.state.OS) && (
+
+
+
+
+
+
+
+
+
+
+ ),
+ macOSVersionLink: (
+
+
+
+ )
+ }}
+ />
+
+
+ )}
diff --git a/src/views/ev3/ev3.jsx b/src/views/ev3/ev3.jsx
index 6666490e9..c6ca1c240 100644
--- a/src/views/ev3/ev3.jsx
+++ b/src/views/ev3/ev3.jsx
@@ -17,6 +17,7 @@ const ExtensionVideo = require('../../components/extension-landing/extension-vid
const ExtensionRequirements = require('../../components/extension-landing/extension-requirements.jsx');
const ExtensionSection = require('../../components/extension-landing/extension-section.jsx');
const InstallScratchLink = require('../../components/extension-landing/install-scratch-link.jsx');
+const InstallScratch = require('../../components/install-scratch/install-scratch.jsx');
const TipBox = require('../../components/extension-landing/tip-box.jsx');
const ProjectCard = require('../../components/extension-landing/project-card.jsx');
@@ -24,6 +25,7 @@ const Steps = require('../../components/steps/steps.jsx');
const Step = require('../../components/steps/step.jsx');
const OS_ENUM = require('../../lib/os-enum.js');
+const {isDownloaded, isFromGooglePlay} = require('../../components/install-scratch/install-util.js');
require('../../components/extension-landing/extension-landing.scss');
require('./ev3.scss');
@@ -60,42 +62,23 @@ class EV3 extends ExtensionLanding {
videoId="0huu6wfiki"
/>}
renderRequirements={
-
-
-
- Windows 10 version 1709+
-
-
-
- macOS 10.13+
-
-
-
- Bluetooth
-
-
-
- Scratch Link
-
-
+
}
/>
-
+ {(isDownloaded(this.state.OS)) && (
+
+ )}
+ {(isFromGooglePlay(this.state.OS)) && (
+
+ )}
@@ -119,20 +102,25 @@ class EV3 extends ExtensionLanding {
/>
-
- Scratch
-
- )
- }}
- />
+ {(isDownloaded(this.state.OS)) && (
+
+ Scratch
+
+ )
+ }}
+ />
+ )}
+ {(isFromGooglePlay(this.state.OS)) && (
+
+ )}
@@ -168,27 +156,51 @@ class EV3 extends ExtensionLanding {
-
-
-
-
- {this.state.OS === OS_ENUM.WINDOWS ?
- :
-
- }
-
+ {this.state.OS === OS_ENUM.WINDOWS && (
+
+
+
+
+
+
+
+
+ )}
+ {this.state.OS === OS_ENUM.MACOS && (
+
+
+
+
+
+
+
+
+ )}
+ {this.state.OS === OS_ENUM.CHROMEOS && (
+
+
+
+
+
+
+
+
+ )}
@@ -269,32 +281,36 @@ class EV3 extends ExtensionLanding {
-
-
-
-
-
- ),
- macOSVersionLink: (
-
-
-
- )
- }}
- />
-
+ {isDownloaded(this.state.OS) && (
+
+
+
+
+
+
+ ),
+ macOSVersionLink: (
+
+
+
+ )
+ }}
+ />
+
+
+ )}
}
renderRequirements={
-
-
-
- Windows 10 version 1709+
-
-
-
- macOS 10.13+
-
-
-
- Bluetooth 4.0
-
-
-
- Scratch Link
-
-
+
}
/>
-
+ {(isDownloaded(this.state.OS)) && (
+
+ )}
+ {(isFromGooglePlay(this.state.OS)) && (
+
+ )}
@@ -117,20 +97,25 @@ class GdxFor extends ExtensionLanding {
/>
-
- Scratch
-
- )
- }}
- />
+ {isDownloaded(this.state.OS) && (
+
+ Scratch
+
+ )
+ }}
+ />
+ )}
+ {isFromGooglePlay(this.state.OS) && (
+
+ )}
@@ -221,32 +206,36 @@ class GdxFor extends ExtensionLanding {
-
-
-
-
-
- ),
- macOSVersionLink: (
-
-
-
- )
- }}
- />
-
+ {isDownloaded(this.state.OS) && (
+
+
+
+
+
+
+ ),
+ macOSVersionLink: (
+
+
+
+ )
+ }}
+ />
+
+
+ )}
diff --git a/src/views/microbit/l10n.json b/src/views/microbit/l10n.json
index fd97d6ef6..4b57e29c2 100644
--- a/src/views/microbit/l10n.json
+++ b/src/views/microbit/l10n.json
@@ -7,6 +7,7 @@
"microbit.downloadCardsTitle": "Download micro:bit Cards",
"microbit.downloadHex": "Download the Scratch micro:bit HEX file",
"microbit.dragDropHex": "Drag and drop the HEX file onto your micro:bit",
+ "microbit.installHexAndroid": "Please follow the instructions to install the HEX file on a computer running Windows, MacOS or ChromeOS.",
"microbit.connectingMicrobit": "Connecting micro:bit to Scratch",
"microbit.powerMicrobit": "Power your micro:bit with USB or a battery pack.",
"microbit.useScratch3": "Use the {scratch3Link} editor.",
diff --git a/src/views/microbit/microbit.jsx b/src/views/microbit/microbit.jsx
index ac274423b..9e9beda27 100644
--- a/src/views/microbit/microbit.jsx
+++ b/src/views/microbit/microbit.jsx
@@ -10,12 +10,14 @@ const render = require('../../lib/render.jsx');
const FlexRow = require('../../components/flex-row/flex-row.jsx');
const OSChooser = require('../../components/os-chooser/os-chooser.jsx');
+const {isDownloaded, isFromGooglePlay} = require('../../components/install-scratch/install-util.js');
const ExtensionLanding = require('../../components/extension-landing/extension-landing.jsx');
const ExtensionHeader = require('../../components/extension-landing/extension-header.jsx');
const ExtensionRequirements = require('../../components/extension-landing/extension-requirements.jsx');
const ExtensionSection = require('../../components/extension-landing/extension-section.jsx');
const InstallScratchLink = require('../../components/extension-landing/install-scratch-link.jsx');
+const InstallScratch = require('../../components/install-scratch/install-scratch.jsx');
const ProjectCard = require('../../components/extension-landing/project-card.jsx');
const Button = require('../../components/forms/button.jsx');
@@ -59,90 +61,96 @@ class MicroBit extends ExtensionLanding {
src="/images/microbit/microbit-heart.png"
/>}
renderRequirements={
-
-
-
- Windows 10 version 1709+
-
-
-
- macOS 10.13+
-
-
-
- Bluetooth 4.0
-
-
-
- Scratch Link
-
-
+
}
/>
-
+ {(isDownloaded(this.state.OS)) && (
+
+ )}
+ {(isFromGooglePlay(this.state.OS)) && (
+
+ )}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ {this.state.OS !== OS_ENUM.ANDROID && (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {this.state.OS === OS_ENUM.WINDOWS && (
+
+
+
+ )}
+ {this.state.OS === OS_ENUM.MACOS && (
+
+
+
+ )}
+ {this.state.OS === OS_ENUM.CHROMEOS && (
+
+
+
+ )}
+
+
+
+
+
+ )}
+ {this.state.OS === OS_ENUM.ANDROID && (
+
+
+
+
+
+
+
+ )}
+
@@ -166,20 +174,25 @@ class MicroBit extends ExtensionLanding {
/>
-
- Scratch
-
- )
- }}
- />
+ {isDownloaded(this.state.OS) && (
+
+ Scratch
+
+ )
+ }}
+ />
+ )}
+ {isFromGooglePlay(this.state.OS) && (
+
+ )}
@@ -307,32 +320,36 @@ class MicroBit extends ExtensionLanding {
-
-
-
-
-
- ),
- macOSVersionLink: (
-
-
-
- )
- }}
- />
-
+ {isDownloaded(this.state.OS) && (
+
+
+
+
+
+
+ ),
+ macOSVersionLink: (
+
+
+
+ )
+ }}
+ />
+
+
+ )}
diff --git a/src/views/wedo2/wedo2.jsx b/src/views/wedo2/wedo2.jsx
index 21506aa64..3a4714cfe 100644
--- a/src/views/wedo2/wedo2.jsx
+++ b/src/views/wedo2/wedo2.jsx
@@ -9,6 +9,7 @@ const render = require('../../lib/render.jsx');
const FlexRow = require('../../components/flex-row/flex-row.jsx');
const OSChooser = require('../../components/os-chooser/os-chooser.jsx');
+const {isDownloaded, isFromGooglePlay} = require('../../components/install-scratch/install-util.js');
const ExtensionLanding = require('../../components/extension-landing/extension-landing.jsx');
const ExtensionHeader = require('../../components/extension-landing/extension-header.jsx');
@@ -16,6 +17,7 @@ const ExtensionVideo = require('../../components/extension-landing/extension-vid
const ExtensionRequirements = require('../../components/extension-landing/extension-requirements.jsx');
const ExtensionSection = require('../../components/extension-landing/extension-section.jsx');
const InstallScratchLink = require('../../components/extension-landing/install-scratch-link.jsx');
+const InstallScratch = require('../../components/install-scratch/install-scratch.jsx');
const ProjectCard = require('../../components/extension-landing/project-card.jsx');
const Steps = require('../../components/steps/steps.jsx');
@@ -57,45 +59,23 @@ class Wedo2 extends ExtensionLanding {
/>
}
renderRequirements={
-
-
-
- Windows 10 version 1709+
-
-
-
- macOS 10.13+
-
-
-
- Bluetooth 4.0
-
-
-
- Scratch Link
-
-
+
}
/>
-
+ {(isDownloaded(this.state.OS)) && (
+
+ )}
+ {(isFromGooglePlay(this.state.OS)) && (
+
+ )}
@@ -110,20 +90,25 @@ class Wedo2 extends ExtensionLanding {
/>
-
- Scratch
-
- )
- }}
- />
+ {isDownloaded(this.state.OS) && (
+
+ Scratch
+
+ )
+ }}
+ />
+ )}
+ {isFromGooglePlay(this.state.OS) && (
+
+ )}
@@ -207,32 +192,36 @@ class Wedo2 extends ExtensionLanding {
-
-
-
-
-
- ),
- macOSVersionLink: (
-
-
-
- )
- }}
- />
-
+ {isDownloaded(this.state.OS) && (
+
+
+
+
+
+
+ ),
+ macOSVersionLink: (
+
+
+
+ )
+ }}
+ />
+
+
+ )}
diff --git a/static/images/ev3/chromeos-enter-passcode.png b/static/images/ev3/chromeos-enter-passcode.png
new file mode 100644
index 0000000000000000000000000000000000000000..0177172ec47263bb65696ee3bfa12e812003b404
GIT binary patch
literal 32951
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%0AGbmcDk*rs8c=zMvS59_E|-Jt9UU&
zA`@ATau^+!3$dp9%WnZtMU?3+^8{*g%n1^zT%_tp4>?K>ktUY54Q2q3h97tZ;NHc6LHVfW*F
zK2OP{f5@%74G6SjF$vt
UY4%wb*7F-hSv8qjDYGB{8=NAd`2YX_
literal 0
HcmV?d00001
diff --git a/static/images/microbit/chromeos-copy-hex.png b/static/images/microbit/chromeos-copy-hex.png
new file mode 100644
index 0000000000000000000000000000000000000000..8e6ad61159fa8508ae33ec1887c52010094798f7
GIT binary patch
literal 54352
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