Merge pull request #8915 from scratchfoundation/release/2024-11-12

[Develop] release/2024-11-12
This commit is contained in:
Miroslav Dionisiev 2024-11-14 17:49:51 +02:00 committed by GitHub
commit bf3608bfd6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 124 additions and 196 deletions

View file

@ -1,19 +1,27 @@
@import "../../../colors";
@import "../../colors";
.driver-popover.tutorials-highlight,
.driver-popover.project-journey {
display: flex;
flex-direction: column;
display: flex !important;
flex-direction: row-reverse;
justify-content: space-between;
align-items: center;
background-color: $ui-purple-dark;
padding: 0.5rem;
.driver-popover-close-btn {
display: flex !important;
justify-content: center;
align-items: center;
position: relative;
height: 2.5rem;
width: 2.5rem;
border-radius: 50%;
margin: 0.5rem;
font-size: 2rem;
font-weight: bold;
color: $type-white;
background-color: $ui-purple-dark;
.close-btn-img {
height: 1.25rem;
width: 1.25rem;
}
}
.driver-popover-arrow-side-left.driver-popover-arrow {

View file

@ -20,6 +20,19 @@ const DriverJourney = ({configProps, driverObj}) => {
callback();
}
const portalData = [];
const closeButton = document.getElementsByClassName('driver-popover-close-btn')[0];
if (closeButton) {
closeButton.textContent = '';
const closeButtonImage = document.createElement('img');
closeButtonImage.src = '/svgs/modal/close-x.svg';
closeButtonImage.className = 'close-btn-img';
closeButton.appendChild(closeButtonImage);
closeButton.addEventListener('click', () => driverObj.destroy());
}
for (const [section, component] of Object.entries(
sectionComponents
)) {
@ -62,7 +75,8 @@ DriverJourney.propTypes = {
}),
driverObj: PropTypes.shape({
setConfig: PropTypes.func,
drive: PropTypes.func
drive: PropTypes.func,
destroy: PropTypes.func
})
};

View file

@ -3,96 +3,13 @@ const {driver} = require('driver.js');
const FlexRow = require('../../flex-row/flex-row.jsx');
const Button = require('../../forms/button.jsx');
const DriverJourney = require('../driver-journey/driver-journey.jsx');
const {defineMessages, useIntl} = require('react-intl');
const {useIntl} = require('react-intl');
const {useMemo, useState, useCallback} = require('react');
const PropTypes = require('prop-types');
const {triggerAnalyticsEvent} = require('../../../lib/onboarding.js');
require('./editor-journey.scss');
const messages = defineMessages({
createStepTitle: {
id: 'gui.journey.controls.create',
defaultMessage: 'Create',
description: 'Create step title'
},
projectGenreStepTitle: {
id: 'gui.journey.controls.choose.projectGenre',
defaultMessage: 'What do you whant to create?',
description: 'Choose project genre step title'
},
typeStepTitle: {
id: 'gui.journey.controls.choose.type',
defaultMessage: 'Which type?',
description: 'Choose project type step title'
},
startStepTitle: {
id: 'gui.journey.controls.choose.start',
defaultMessage: 'How do you want to start?',
description: 'Choose way to start step title'
},
gameButtonText: {
id: 'gui.journey.controls.game',
defaultMessage: 'Game',
description: 'Game button text'
},
animiationButtonText: {
id: 'gui.journey.controls.animation',
defaultMessage: 'Animation',
description: 'Animation button text'
},
musicButtonText: {
id: 'gui.journey.controls.music',
defaultMessage: 'Music',
description: 'Music button text'
},
clickerGameButtonText: {
id: 'gui.journey.controls.game.clicker',
defaultMessage: 'Clicker Game',
description: 'Clicker game button text'
},
pongGameButtonText: {
id: 'gui.journey.controls.game.pong',
defaultMessage: 'Pong Game',
description: 'Pong game button text'
},
characterAnimationButtonText: {
id: 'gui.journey.controls.animation.character',
defaultMessage: 'Animate a character',
description: 'Animate a character button text'
},
flyAnimationButtonText: {
id: 'gui.journey.controls.animation.fly',
defaultMessage: 'Make it fly',
description: 'Make it fly animation button text'
},
recordSoundButtonText: {
id: 'gui.journey.controls.music.record',
defaultMessage: 'Record a sound',
description: 'Record a sound button text'
},
makeMusicButtonText: {
id: 'gui.journey.controls.music.make',
defaultMessage: 'Make music',
description: 'Make music button text'
},
tutorialButtonText: {
id: 'gui.journey.controls.tutorial',
defaultMessage: 'Tutorial',
description: 'Tutorial button text'
},
starterProjectButtonText: {
id: 'gui.journey.controls.starterProject',
defaultMessage: 'Starter project',
description: 'Starter project button text'
},
onMyOwnButtonText: {
id: 'gui.journey.controls.onMyOwn',
defaultMessage: 'On my own',
description: 'On my own button text'
}
});
const STEP_NAMES = [
'pick-genre-step',
'game-step',
@ -151,18 +68,18 @@ const EditorJourney = ({onActivateDeck, setCanViewTutorialsHighlight, setShowJou
editorJourneyStep: editorJourneyStep
});
driverObj.moveTo(stepNumber);
}, driverObj);
}, [driverObj]);
const createStep = useCallback((projectId, tutorialId) => ({
title: intl.formatMessage(messages.createStepTitle),
title: intl.formatMessage({id: 'project.journey.controls.create'}),
showButtons: ['close'],
sectionComponents: {
description: <EditorJourneyDescription
title={intl.formatMessage(messages.startStepTitle)}
title={intl.formatMessage({id: 'project.journey.controls.choose.start'})}
descriptionData={[
{
imgSrc: '/images/onboarding-journeys/Tutorials-Icon.svg',
text: intl.formatMessage(messages.tutorialButtonText),
text: intl.formatMessage({id: 'project.journey.controls.tutorial'}),
handleOnClick: () => {
triggerAnalyticsEvent({
event: 'editor-journey-step',
@ -175,7 +92,7 @@ const EditorJourney = ({onActivateDeck, setCanViewTutorialsHighlight, setShowJou
},
{
imgSrc: '/images/onboarding-journeys/Starter-Projects-Icon.svg',
text: intl.formatMessage(messages.starterProjectButtonText),
text: intl.formatMessage({id: 'project.journey.controls.starterProject'}),
handleOnClick: () => {
location.href = `/projects/${projectId}?showJourney=true`;
setShowJourney(false);
@ -184,7 +101,7 @@ const EditorJourney = ({onActivateDeck, setCanViewTutorialsHighlight, setShowJou
},
{
imgSrc: '/images/onboarding-journeys/On-Own-Icon.svg',
text: intl.formatMessage(messages.onMyOwnButtonText),
text: intl.formatMessage({id: 'project.journey.controls.onMyOwn'}),
handleOnClick: () => {
triggerAnalyticsEvent({
event: 'editor-journey-step',
@ -217,25 +134,25 @@ const EditorJourney = ({onActivateDeck, setCanViewTutorialsHighlight, setShowJou
},
steps: [{
popover: {
title: intl.formatMessage(messages.createStepTitle),
title: intl.formatMessage({id: 'project.journey.controls.create'}),
showButtons: ['close'],
sectionComponents: {
description: <EditorJourneyDescription
title={intl.formatMessage(messages.projectGenreStepTitle)}
title={intl.formatMessage({id: 'project.journey.controls.choose.projectGenre'})}
descriptionData={[
{
imgSrc: '/images/onboarding-journeys/Games-Icon.svg',
text: intl.formatMessage(messages.gameButtonText),
text: intl.formatMessage({id: 'project.journey.controls.game'}),
handleOnClick: () => pickStep(1, 'Games')
},
{
imgSrc: '/images/onboarding-journeys/Animation-Icon.svg',
text: intl.formatMessage(messages.animiationButtonText),
text: intl.formatMessage({id: 'project.journey.controls.animation'}),
handleOnClick: () => pickStep(2, 'Animation')
},
{
imgSrc: '/images/onboarding-journeys/Music-Icon.svg',
text: intl.formatMessage(messages.musicButtonText),
text: intl.formatMessage({id: 'project.journey.controls.music'}),
handleOnClick: () => pickStep(3, 'Music')
}
]}
@ -245,20 +162,20 @@ const EditorJourney = ({onActivateDeck, setCanViewTutorialsHighlight, setShowJou
},
{
popover: {
title: intl.formatMessage(messages.createStepTitle),
title: intl.formatMessage({id: 'project.journey.controls.create'}),
showButtons: ['close'],
sectionComponents: {
description: <EditorJourneyDescription
title={intl.formatMessage(messages.typeStepTitle)}
title={intl.formatMessage({id: 'project.journey.controls.choose.type'})}
descriptionData={[
{
imgSrc: '/images/onboarding-journeys/Clicker-Game.jpg',
text: intl.formatMessage(messages.clickerGameButtonText),
text: intl.formatMessage({id: 'project.journey.controls.game.clicker'}),
handleOnClick: () => pickStep(4, 'Clicker-Game')
},
{
imgSrc: '/images/onboarding-journeys/Pong-Game.jpg',
text: intl.formatMessage(messages.pongGameButtonText),
text: intl.formatMessage({id: 'project.journey.controls.game.pong'}),
handleOnClick: () => pickStep(5, 'Pong-Game')
}
]}
@ -268,20 +185,20 @@ const EditorJourney = ({onActivateDeck, setCanViewTutorialsHighlight, setShowJou
},
{
popover: {
title: intl.formatMessage(messages.createStepTitle),
title: intl.formatMessage({id: 'project.journey.controls.create'}),
showButtons: ['close'],
sectionComponents: {
description: <EditorJourneyDescription
title={intl.formatMessage(messages.typeStepTitle)}
title={intl.formatMessage({id: 'project.journey.controls.choose.type'})}
descriptionData={[
{
imgSrc: '/images/onboarding-journeys/Character-Animation.jpg',
text: intl.formatMessage(messages.characterAnimationButtonText),
text: intl.formatMessage({id: 'project.journey.controls.animation.character'}),
handleOnClick: () => pickStep(6, 'Character-Animation')
},
{
imgSrc: '/images/onboarding-journeys/Fly-Animation.jpg',
text: intl.formatMessage(messages.flyAnimationButtonText),
text: intl.formatMessage({id: 'project.journey.controls.animation.fly'}),
handleOnClick: () => pickStep(7, 'Fly-Animation')
}
]}
@ -291,20 +208,20 @@ const EditorJourney = ({onActivateDeck, setCanViewTutorialsHighlight, setShowJou
},
{
popover: {
title: intl.formatMessage(messages.createStepTitle),
title: intl.formatMessage({id: 'project.journey.controls.create'}),
showButtons: ['close'],
sectionComponents: {
description: <EditorJourneyDescription
title={intl.formatMessage(messages.typeStepTitle)}
title={intl.formatMessage({id: 'project.journey.controls.choose.type'})}
descriptionData={[
{
imgSrc: '/images/onboarding-journeys/Record-Music.jpg',
text: intl.formatMessage(messages.recordSoundButtonText),
text: intl.formatMessage({id: 'project.journey.controls.music.record'}),
handleOnClick: () => pickStep(8, 'Record-Music')
},
{
imgSrc: '/images/onboarding-journeys/Make-Music.jpg',
text: intl.formatMessage(messages.makeMusicButtonText),
text: intl.formatMessage({id: 'project.journey.controls.music.make'}),
handleOnClick: () => pickStep(9, 'Make-Music')
}
]}

View file

@ -2,7 +2,6 @@
@import "../../../frameless";
.driver-popover.gui-journey {
font-family: "Helvetica Neue", "Helvetica", Arial, sans-serif;
max-width: unset;
padding: 0;
border-radius: 15px;
@ -11,20 +10,27 @@
transform: translate(-50%, -50%);
.driver-popover-close-btn {
display: flex !important;
justify-content: center;
align-items: center;
height: 2.5rem;
width: 2.5rem;
border-radius: 50%;
margin: 0.5rem;
font-size: 2rem;
font-weight: bold;
color: $type-white;
background-color: $ui-aqua-dark;
.close-btn-img {
height: 1.25rem;
width: 1.25rem;
}
}
.driver-popover-title {
padding: 1rem 0;
font-size: 1rem;
font-weight: 700;
font-family: "Helvetica Neue", "Helvetica", Arial, sans-serif;
text-align: center;
color: $type-white;
margin: 0;

View file

@ -1,23 +1,10 @@
const React = require('react');
const {driver} = require('driver.js');
const DriverJourney = require('../driver-journey/driver-journey.jsx');
const {defineMessages, useIntl} = require('react-intl');
const {useIntl} = require('react-intl');
const {useState} = require('react');
const PropTypes = require('prop-types');
require('./project-journey.scss');
const messages = defineMessages({
playProject: {
id: 'project.journey.play',
defaultMessage: 'Click the green flag to see what this project does.',
description: 'Play project'
},
remixProject: {
id: 'project.journey.remix',
defaultMessage: 'Make your own version!',
description: 'Remix project'
}
});
require('../common-journey.scss');
const ProjectJourney = ({setCanViewProjectJourney, setShouldStopProject}) => {
const [driverObj] = useState(() => (
@ -40,7 +27,7 @@ const ProjectJourney = ({setCanViewProjectJourney, setShouldStopProject}) => {
}, 8000);
});
},
description: intl.formatMessage(messages.playProject)
description: intl.formatMessage({id: 'project.journey.play'})
}
},
{
@ -53,7 +40,7 @@ const ProjectJourney = ({setCanViewProjectJourney, setShouldStopProject}) => {
driverObj.destroy();
});
},
description: intl.formatMessage(messages.remixProject)
description: intl.formatMessage({id: 'project.journey.remix'})
}
}];

View file

@ -1,18 +1,10 @@
const React = require('react');
const {driver} = require('driver.js');
const DriverJourney = require('../driver-journey/driver-journey.jsx');
const {defineMessages, useIntl} = require('react-intl');
const {useIntl} = require('react-intl');
const PropTypes = require('prop-types');
const {useState} = require('react');
require('./tutorials-highlight.scss');
const messages = defineMessages({
tutorialsHighlight: {
id: 'gui.highlight.tutorials',
defaultMessage: 'Click here for tutorials',
description: 'Tutorials highlight'
}
});
require('../common-journey.scss');
const TutorialsHighlight = ({setCanViewTutorialsHighlight}) => {
const [driverObj] = useState(() => (
@ -33,7 +25,7 @@ const TutorialsHighlight = ({setCanViewTutorialsHighlight}) => {
});
},
side: 'bottom',
description: intl.formatMessage(messages.tutorialsHighlight)
description: intl.formatMessage({id: 'project.highlight.tutorials'})
}
}];

View file

@ -1,41 +0,0 @@
@import "../../../colors";
.driver-popover.tutorials-highlight {
display: flex;
flex-direction: column;
background-color: $ui-purple-dark;
.driver-popover-close-btn {
height: 2.5rem;
width: 2.5rem;
border-radius: 50%;
margin: 0.5rem;
font-size: 2rem;
font-weight: bold;
color: $type-white;
background-color: $ui-purple-dark;
}
.driver-popover-arrow-side-left.driver-popover-arrow {
border-left-color: $ui-purple-dark;;
}
.driver-popover-arrow-side-right.driver-popover-arrow {
border-right-color: $ui-purple-dark;;
}
.driver-popover-arrow-side-top.driver-popover-arrow {
border-top-color: $ui-purple-dark;;
}
.driver-popover-arrow-side-bottom.driver-popover-arrow {
border-bottom-color: $ui-purple-dark;;
}
.driver-popover-description {
color: $ui-white;
font-size: 1rem;
font-weight: 700;
font-family: "Helvetica Neue", "Helvetica", Arial, sans-serif;
}
}

View file

@ -1,5 +1,6 @@
const PropTypes = require('prop-types');
const React = require('react');
const {shouldDisplayOnboarding} = require('../../lib/onboarding.js');
const Box = require('../box/box.jsx');
@ -19,11 +20,23 @@ const Welcome = props => (
>
<div className="welcome-col blue">
<h4>
<a href="/projects/editor/?tutorial=getStarted">
<a
href={
shouldDisplayOnboarding(props.user, props.permissions) ?
'/projects/editor/' :
'/projects/editor/?tutorial=getStarted'
}
>
{props.messages['welcome.learn']}
</a>
</h4>
<a href="/projects/editor/?tutorial=getStarted">
<a
href={
shouldDisplayOnboarding(props.user, props.permissions) ?
'/projects/editor/' :
'/projects/editor/?tutorial=getStarted'
}
>
<img
alt="Get Started"
src="/images/welcome-learn.png"
@ -66,7 +79,9 @@ Welcome.propTypes = {
'welcome.tryOut': PropTypes.string,
'welcome.connect': PropTypes.string
}),
onDismiss: PropTypes.func
onDismiss: PropTypes.func,
permissions: PropTypes.object,
user: PropTypes.object
};
Welcome.defaultProps = {

View file

@ -39,9 +39,14 @@ const isUserEligible = (user, permissions) =>
!isBanned(user);
const calculateAgeGroup = (birthYear, birthMonth) => {
if (!birthMonth || !birthYear) {
return '[unset]';
}
const today = new Date();
let age = today.getFullYear() - parseInt(birthYear, 10);
const monthDiff = today.getMonth() + 1 - parseInt(birthMonth, 10);
let age = today.getFullYear() - birthYear;
const monthDiff = today.getMonth() + 1 - birthMonth;
if (monthDiff < 0) {
age--;
}
@ -71,6 +76,8 @@ export const triggerAnalyticsEvent = eventVaribles => {
};
export const sendUserProperties = (user, permissions) => {
window.dataLayer = window.dataLayer || [];
if (!isUserEligible(user, permissions)) {
window.dataLayer.push({
testGroup: null,
@ -80,8 +87,6 @@ export const sendUserProperties = (user, permissions) => {
return;
}
window.dataLayer = window.dataLayer || [];
const {gender, birthYear, birthMonth} = user;
window.dataLayer.push({

View file

@ -48,5 +48,24 @@
"project.usernameBlockAlert": "This project can detect who is using it, through the \"username\" block. To hide your identity, sign out before using the project.",
"project.inappropriateUpdate": "Hmm...the bad word detector thinks there is a problem with your text. Please change it and remember to be respectful.",
"project.mutedAddToStudio": "You will be able to add to studios again {inDuration}.",
"project.cloudDataAndVideoAlert": "For privacy reasons, cloud variables have been disabled in this project because it contains video sensing blocks."
"project.cloudDataAndVideoAlert": "For privacy reasons, cloud variables have been disabled in this project because it contains video sensing blocks.",
"project.journey.controls.create": "Create",
"project.journey.controls.choose.projectGenre": "What do you want to create?",
"project.journey.controls.choose.type": "Which type?",
"project.journey.controls.choose.start": "How do you want to start?",
"project.journey.controls.game": "Game",
"project.journey.controls.animation": "Animation",
"project.journey.controls.music": "Music",
"project.journey.controls.game.clicker": "Clicker Game",
"project.journey.controls.game.pong": "Pong Game",
"project.journey.controls.animation.character": "Animate a character",
"project.journey.controls.animation.fly": "Make it fly",
"project.journey.controls.music.record": "Record a sound",
"project.journey.controls.music.make": "Make music",
"project.journey.controls.tutorial": "Tutorial",
"project.journey.controls.starterProject": "Starter project",
"project.journey.controls.onMyOwn": "On my own",
"project.highlight.tutorials": "Click here for tutorials",
"project.journey.play": "Click the green flag to see what this project does.",
"project.journey.remix": "Make your own version!"
}

View file

@ -418,6 +418,8 @@ class SplashPresentation extends React.Component { // eslint-disable-line react/
onDismiss={() => { // eslint-disable-line react/jsx-no-bind
this.props.onDismiss('welcome');
}}
permissions={this.props.permissions}
user={this.props.user}
/>
] : [
<WrappedActivityList
@ -544,6 +546,7 @@ SplashPresentation.propTypes = {
onDismiss: PropTypes.func.isRequired,
onOpenAdminPanel: PropTypes.func.isRequired,
onRefreshHomepageCache: PropTypes.func.isRequired,
permissions: PropTypes.object,
refreshCacheStatus: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
sessionStatus: PropTypes.string.isRequired,
sharedByFollowing: PropTypes.arrayOf(PropTypes.object),

View file

@ -218,6 +218,7 @@ class Splash extends React.Component {
isEducator={this.props.isEducator}
lovedByFollowing={this.props.loved}
news={this.state.news}
permissions={this.props.permissions}
refreshCacheStatus={homepageRefreshStatus}
sessionStatus={this.props.sessionStatus}
sharedByFollowing={this.props.shared}
@ -267,6 +268,7 @@ Splash.propTypes = {
isAdmin: PropTypes.bool,
isEducator: PropTypes.bool,
loved: PropTypes.arrayOf(PropTypes.object).isRequired,
permissions: PropTypes.object,
refreshSession: PropTypes.func.isRequired,
reviewCommunityGuidelines: PropTypes.func.isRequired,
sessionStatus: PropTypes.string,
@ -302,6 +304,7 @@ const mapStateToProps = state => ({
isAdmin: state.permissions.admin,
isEducator: state.permissions.educator,
loved: state.splash.loved.rows,
permissions: state.permissions,
sessionStatus: state.session.status,
shared: state.splash.shared.rows,
studios: state.splash.studios.rows,