diff --git a/src/components/debug-modal/debug-modal.css b/src/components/debug-modal/debug-modal.css new file mode 100644 index 00000000000..1125bf45860 --- /dev/null +++ b/src/components/debug-modal/debug-modal.css @@ -0,0 +1,162 @@ +@import "../../css/colors.css"; + +.debug-modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: 'transparent'; + display: flex; + justify-content: center; + align-items: center; + z-index: 510; +} + +.debug-modal-container { + background: white; + border-radius: 8px; + width: 1000px; + max-height: 90%; + display: flex; + flex-direction: column; + position: relative; + overflow-x: visible; + box-shadow: 0 4px 4px 0 $ui-black-transparent-10; + outline: none; + + .modal-header { + display: flex; + border-radius: 8px 8px 0 0; + justify-content: space-between; + align-items: center; + padding: 8px; + padding-left: 12px; + padding-right: 12px; + background-color: $ui-green-2; + } + + .header-title { + display: flex; + gap: 8px; + align-items: center; + font-size: 1rem; + line-height: 1.25rem; + font-weight: 700; + color: white; + } + .debug-icon { + height: 22px; + width: 22px; + } + + .hidden { + display: none; + } + + .close-button { + display: flex; + background: none; + border: none; + cursor: pointer; + width: 32px; + height: 32px; + } + + .modal-content { + display: flex; + width: 100%; + flex-grow: 1; + overflow-y: scroll; + } + + .modal-content::-webkit-scrollbar-track { + background: transparent; + } + + .modal-content::-webkit-scrollbar { + width: 8px; + } + + .previousIcon { + position: absolute; + cursor: pointer; + top: 50%; + } + + .nextIcon { + position: absolute; + cursor: pointer; + right: -24px; + top: 50%; + } + + .topic-list { + width: 30%; + border-right: 1px solid $ui-green;; + } + + .topic-item { + display: flex; + gap: 8px; + align-items: center; + padding: 8px; + padding-left: 12px; + font-size: 1rem; + line-height: 1.5rem; + color: $ui-green;; + cursor: pointer; + } + + .topic-item.active { + background-color: #D1FAEE; + font-weight: bold; + } + + .info-container { + flex-direction: column; + width: 70%; + display: flex; + padding: 20px; + color: $text-primary; + } + + .text-container { + flex: 1; + margin-left: 70px; + } + + .title-text { + font-size: 24px; + line-height: 32px; + font-weight: 700; + } + + .description { + font-size: 16px; + line-height: 28px; + } + + .imageContainer { + display: flex; + justify-content: center; + align-items: center; + padding: 10px; + margin-top: 10px; + } + + .topicImage { + max-width: 100%; + max-height: 100%; + object-fit: contain; /* Ensures image scales proportionally */ + } + + .navigation-buttons { + margin-top: 20px; + } + + button { + margin: 5px; + } +} + diff --git a/src/components/debug-modal/debug-modal.jsx b/src/components/debug-modal/debug-modal.jsx new file mode 100644 index 00000000000..e79a6195fec --- /dev/null +++ b/src/components/debug-modal/debug-modal.jsx @@ -0,0 +1,174 @@ +import React, {useState, useCallback, useEffect} from 'react'; +import {defineMessages, FormattedMessage} from 'react-intl'; +import PropTypes from 'prop-types'; +import ReactModal from 'react-modal'; +import classNames from 'classnames'; +import {sections} from './sections/sections'; +import GA4 from '../../lib/analytics'; + +import styles from './debug-modal.css'; +import debugIcon from './icons/icon--debug.svg'; +import debugIconInverted from './icons/icon--debug-inverted.svg'; +import closeIcon from './icons/icon--close.svg'; +import prevIcon from './icons/icon--prev.svg'; +import nextIcon from './icons/icon--next.svg'; + +const messages = defineMessages({ + title: { + id: 'gui.debugModal.title', + defaultMessage: 'Debugging | Getting Unstuck', + description: 'title for the debugging modal' + } +}); + +const logTopicChange = topicIndex => { + GA4.event({ + category: 'change_topic_debug_modal', + label: sections[topicIndex].id + }); +}; + +const DebugModal = ({isOpen, onClose = () => {}}) => { + const [selectedTopicIndex, setSelectedTopicIndex] = useState(0); + + // Preload images + useEffect(() => { + sections.forEach(section => { + new Image().src = section.image; + }); + }, []); + + const handleNext = useCallback(() => { + if (selectedTopicIndex < sections.length - 1) { + setSelectedTopicIndex(selectedTopicIndex + 1); + logTopicChange(selectedTopicIndex + 1); + } + }, [selectedTopicIndex, setSelectedTopicIndex]); + + const handlePrevious = useCallback(() => { + if (selectedTopicIndex > 0) { + setSelectedTopicIndex(selectedTopicIndex - 1); + logTopicChange(selectedTopicIndex - 1); + } + }, [selectedTopicIndex, setSelectedTopicIndex]); + + const handleTopicSelect = useCallback(index => { + setSelectedTopicIndex(index); + logTopicChange(index); + }, [setSelectedTopicIndex]); + + const handleClose = useCallback(() => { + GA4.event({ + category: 'close_debug_modal' + }); + onClose(); + }, [onClose]); + + useEffect(() => { + if (isOpen) { + GA4.event({ + category: 'open_debug_modal', + label: sections[selectedTopicIndex].id + }); + } + }, [isOpen]); + + if (!isOpen) return null; + + return ( + +
+
+ + +
+ +
+
+
+ {sections.map((section, index) => ( +
handleTopicSelect(index)} + > +
+ +
+ +
+ ))} +
+
+
+
+ +
+
{sections[selectedTopicIndex].description}
+
+
+ +
+
+ Previous + Next +
+
+
+
+ ); +}; + +DebugModal.propTypes = { + isOpen: PropTypes.bool, + onClose: PropTypes.func +}; + +export default DebugModal; diff --git a/src/components/debug-modal/icons/icon--add-sound-checkpoints.svg b/src/components/debug-modal/icons/icon--add-sound-checkpoints.svg new file mode 100644 index 00000000000..4b6f65bfe0d --- /dev/null +++ b/src/components/debug-modal/icons/icon--add-sound-checkpoints.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/components/debug-modal/icons/icon--ask-for-help.svg b/src/components/debug-modal/icons/icon--ask-for-help.svg new file mode 100644 index 00000000000..1f59b547318 --- /dev/null +++ b/src/components/debug-modal/icons/icon--ask-for-help.svg @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/debug-modal/icons/icon--break-it-down.svg b/src/components/debug-modal/icons/icon--break-it-down.svg new file mode 100644 index 00000000000..7d1d104223a --- /dev/null +++ b/src/components/debug-modal/icons/icon--break-it-down.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/debug-modal/icons/icon--check-code-sequence.svg b/src/components/debug-modal/icons/icon--check-code-sequence.svg new file mode 100644 index 00000000000..f458487e175 --- /dev/null +++ b/src/components/debug-modal/icons/icon--check-code-sequence.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/debug-modal/icons/icon--check-the-values.svg b/src/components/debug-modal/icons/icon--check-the-values.svg new file mode 100644 index 00000000000..9918a57b896 --- /dev/null +++ b/src/components/debug-modal/icons/icon--check-the-values.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/debug-modal/icons/icon--close.svg b/src/components/debug-modal/icons/icon--close.svg new file mode 100644 index 00000000000..e12cf933df8 --- /dev/null +++ b/src/components/debug-modal/icons/icon--close.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/debug-modal/icons/icon--comment-your-code.svg b/src/components/debug-modal/icons/icon--comment-your-code.svg new file mode 100644 index 00000000000..6f2acd9bd9c --- /dev/null +++ b/src/components/debug-modal/icons/icon--comment-your-code.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/components/debug-modal/icons/icon--debug-inverted.svg b/src/components/debug-modal/icons/icon--debug-inverted.svg new file mode 100644 index 00000000000..1c9a1d4ab48 --- /dev/null +++ b/src/components/debug-modal/icons/icon--debug-inverted.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/debug-modal/icons/icon--debug.svg b/src/components/debug-modal/icons/icon--debug.svg new file mode 100644 index 00000000000..6d192179918 --- /dev/null +++ b/src/components/debug-modal/icons/icon--debug.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/debug-modal/icons/icon--next.svg b/src/components/debug-modal/icons/icon--next.svg new file mode 100644 index 00000000000..f2a10ae158c --- /dev/null +++ b/src/components/debug-modal/icons/icon--next.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/components/debug-modal/icons/icon--prev.svg b/src/components/debug-modal/icons/icon--prev.svg new file mode 100644 index 00000000000..ddf932d0308 --- /dev/null +++ b/src/components/debug-modal/icons/icon--prev.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/components/debug-modal/icons/icon--read-aloud.svg b/src/components/debug-modal/icons/icon--read-aloud.svg new file mode 100644 index 00000000000..e6a98660701 --- /dev/null +++ b/src/components/debug-modal/icons/icon--read-aloud.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/components/debug-modal/icons/icon--slow-it-down.svg b/src/components/debug-modal/icons/icon--slow-it-down.svg new file mode 100644 index 00000000000..e5c04a9af6b --- /dev/null +++ b/src/components/debug-modal/icons/icon--slow-it-down.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/debug-modal/icons/icon--take-a-break.svg b/src/components/debug-modal/icons/icon--take-a-break.svg new file mode 100644 index 00000000000..37b973b88ff --- /dev/null +++ b/src/components/debug-modal/icons/icon--take-a-break.svg @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/debug-modal/icons/icon--think-about-block-options.svg b/src/components/debug-modal/icons/icon--think-about-block-options.svg new file mode 100644 index 00000000000..1896cdddd88 --- /dev/null +++ b/src/components/debug-modal/icons/icon--think-about-block-options.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/debug-modal/icons/icon--timing-and-parallelism.svg b/src/components/debug-modal/icons/icon--timing-and-parallelism.svg new file mode 100644 index 00000000000..fbbf09c3381 --- /dev/null +++ b/src/components/debug-modal/icons/icon--timing-and-parallelism.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/debug-modal/icons/icon--tinker-with-block-order.svg b/src/components/debug-modal/icons/icon--tinker-with-block-order.svg new file mode 100644 index 00000000000..406ab56427d --- /dev/null +++ b/src/components/debug-modal/icons/icon--tinker-with-block-order.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/debug-modal/icons/icon--to-loop-or-not.svg b/src/components/debug-modal/icons/icon--to-loop-or-not.svg new file mode 100644 index 00000000000..25787b6bcb2 --- /dev/null +++ b/src/components/debug-modal/icons/icon--to-loop-or-not.svg @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/debug-modal/sections/messages.ts b/src/components/debug-modal/sections/messages.ts new file mode 100644 index 00000000000..72c2acdc0e9 --- /dev/null +++ b/src/components/debug-modal/sections/messages.ts @@ -0,0 +1,296 @@ +import {defineMessages} from 'react-intl'; + +export const messages = defineMessages({ + readAloudTitle: { + id: 'gui.debugModal.readAloud.title', + defaultMessage: 'Read Aloud', + description: 'title for the "read aloud" section' + }, + readAloudDescription1: { + id: 'gui.debugModal.readAloud.description1', + defaultMessage: 'As you read your code aloud, think from the computer’s perspective.', + description: 'description for the "read aloud" section of the debug modal' + }, + readAloudDescription2: { + id: 'gui.debugModal.readAloud.description2', + defaultMessage: 'Are you including steps that aren’t there?', + description: 'description for the "read aloud" section of the debug modal' + }, + readAloudDescription3: { + id: 'gui.debugModal.readAloud.description3', + defaultMessage: 'Are your instructions clear?', + description: 'description for the "read aloud" section of the debug modal' + }, + readAloudDescription4: { + id: 'gui.debugModal.readAloud.description4', + defaultMessage: + 'If something needs to be reset each time the program has run, are those instructions included?', + description: 'description for the "read aloud" section of the debug modal' + }, + breakItDownTitle: { + id: 'gui.debugModal.breakItDown.title', + defaultMessage: 'Break It Down', + description: 'title for the "break it down" section' + }, + breakItDownDescription1: { + id: 'gui.debugModal.breakItDown.description1', + defaultMessage: + 'Separate the blocks into smaller chunks (or sequences), and click to see what each sequence does.', + description: 'description for the "break it down" section of the debug modal' + }, + breakItDownDescription2: { + id: 'gui.debugModal.breakItDown.description2', + defaultMessage: + 'Once the smaller sequences work as you expect, add them back into the main program.', + description: 'description for the "break it down" section of the debug modal' + }, + breakItDownDescription3: { + id: 'gui.debugModal.breakItDown.description3', + defaultMessage: 'The process is called decomposition.', + description: 'description for the "break it down" section of the debug modal' + }, + slowItDownTitle: { + id: 'gui.debugModal.slowItDown.title', + defaultMessage: 'Slow It Down', + description: 'title for the "slow it down" section' + }, + slowItDownDescription1: { + id: 'gui.debugModal.slowItDown.description1', + defaultMessage: + 'The computer runs your program so quickly it can be hard to follow with your eyes.', + description: 'description for the "slow it down" section of the debug modal' + }, + slowItDownDescription2: { + id: 'gui.debugModal.slowItDown.description2', + defaultMessage: + // eslint-disable-next-line max-len + 'Add temporary “wait” or “wait until” blocks to slow down the sequence. This gives you time to process if a piece worked or not.', + description: 'description for the "slow it down" section of the debug modal' + }, + slowItDownDescription3: { + id: 'gui.debugModal.slowItDown.description3', + defaultMessage: 'Remove these wait blocks once your code works.', + description: 'description for the "slow it down" section of the debug modal' + }, + addSoundCheckpointsTitle: { + id: 'gui.debugModal.addSoundCheckpoints.title', + defaultMessage: 'Add Sound Checkpoints', + description: 'title for the "add sound checkpoints" section' + }, + addSoundCheckpointsDescription1: { + id: 'gui.debugModal.addSoundCheckpoints.description1', + defaultMessage: + // eslint-disable-next-line max-len + 'Similar to the Slow It Down strategy, you can add different sounds with the “play until done” block at key points to test your sequence.', + description: 'description for the "add sound checkpoints" section of the debug modal' + }, + addSoundCheckpointsDescription2: { + id: 'gui.debugModal.addSoundCheckpoints.description2', + defaultMessage: + // eslint-disable-next-line max-len + 'If a sound doesn’t play, your bug may be before this block. If the sound plays, the bug is probably after this block.', + description: 'description for the "add sound checkpoints" section of the debug modal' + }, + addSoundCheckpointsDescription3: { + id: 'gui.debugModal.addSoundCheckpoints.description3', + defaultMessage: 'Remove the sounds once your code works.', + description: 'description for the "add sound checkpoints" section of the debug modal' + }, + tinkerWithBlockOrderTitle: { + id: 'gui.debugModal.tinkerWithBlockOrder.title', + defaultMessage: 'Tinker with Block Order', + description: 'title for the "tinker with block order" section' + }, + tinkerWithBlockOrderDescription1: { + id: 'gui.debugModal.tinkerWithBlockOrder.description1', + defaultMessage: 'Try adjusting the order/sequence of the blocks.', + description: 'description for the "tinker with block order" section of the debug modal' + }, + tinkerWithBlockOrderDescription2: { + id: 'gui.debugModal.tinkerWithBlockOrder.description2', + defaultMessage: 'What needs to happen first?', + description: 'description for the "tinker with block order" section of the debug modal' + }, + tinkerWithBlockOrderDescription3: { + id: 'gui.debugModal.tinkerWithBlockOrder.description3', + defaultMessage: 'What happens second?', + description: 'description for the "tinker with block order" section of the debug modal' + }, + tinkerWithBlockOrderDescription4: { + id: 'gui.debugModal.tinkerWithBlockOrder.description4', + defaultMessage: 'Do values or sprites need to reset before the next piece of code runs?', + description: 'description for the "tinker with block order" section of the debug modal' + }, + tinkerWithBlockOrderDescription5: { + id: 'gui.debugModal.tinkerWithBlockOrder.description5', + defaultMessage: + 'Try using blocks inside a loop or conditional statement, versus outside a loop or conditional statement.', + description: 'description for the "tinker with block order" section of the debug modal' + }, + toLoopOrNotTitle: { + id: 'gui.debugModal.toLoopOrNot.title', + defaultMessage: 'To Loop or Not to Loop', + description: 'title for the "tinker with block order" section' + }, + toLoopOrNotDescription1: { + id: 'gui.debugModal.toLoopOrNot.description1', + defaultMessage: + // eslint-disable-next-line max-len + 'If using Control blocks like "forever" and "repeat", check that all blocks inside a loop should be there, or if a block (like “wait”) is missing to reset the action or adjust the timing. Do you want your loop to run forever or for a certain number of times? Should something stop the looping?', + description: 'description for the "to loop or not to loop" section of the debug modal' + }, + toLoopOrNotDescription2: { + id: 'gui.debugModal.toLoopOrNot.description2', + defaultMessage: + // eslint-disable-next-line max-len + 'Perhaps you aren\'t using a loop when you should be? For instance, if you are using a conditional statement block like "if then," does the program only need to check if it is true or false once? Or does it need to check continuously, in which case, you would want to place your conditional statement inside a forever loop?', + description: 'description for the "to loop or not to loop" section of the debug modal' + }, + timingAndParallelismTitle: { + id: 'gui.debugModal.timingAndParallelism.title', + defaultMessage: 'Think About Timing & Parallelism', + description: 'title for the "think about timing and parallelism" section' + }, + timingAndParallelismSectionTitle: { + id: 'gui.debugModal.timingAndParallelism.sectionTitle', + defaultMessage: 'Timing & Parallelism', + description: 'title for the "think about timing and parallelism" sidebar section' + }, + timingAndParallelismDescription1: { + id: 'gui.debugModal.timingAndParallelism.description1', + defaultMessage: + // eslint-disable-next-line max-len + 'Do you have multiple events trying to run at the same time? If two sequences are programmed to start at the same time, you can get unpredictable behavior.', + description: 'description for the "think about timing and parallelism" section of the debug modal' + }, + timingAndParallelismDescription2: { + id: 'gui.debugModal.timingAndParallelism.description2', + defaultMessage: + // eslint-disable-next-line max-len + 'Add small waits, broadcasts, or user interaction (like clicking or pressing a key) to see if this affects the result.', + description: 'description for the "think about timing and parallelism" section of the debug modal' + }, + thinkAboutBlockOptionsTitle: { + id: 'gui.debugModal.thinkAboutBlockOptions.title', + defaultMessage: 'Think About Block Options', + description: 'title for the "think about block options" section' + }, + thinkAboutBlockOptionsDescription1: { + id: 'gui.debugModal.thinkAboutBlockOptions.description1', + defaultMessage: 'Is there a similar but different block you can use?', + description: 'description for the "think about block options" section of the debug modal' + }, + thinkAboutBlockOptionsDescription2: { + id: 'gui.debugModal.thinkAboutBlockOptions.description2', + defaultMessage: + // eslint-disable-next-line max-len + 'Some blocks look similar but can behave differently, such as “set” vs “change” or “play until done” vs “start.”', + description: 'description for the "think about block options" section of the debug modal' + }, + thinkAboutBlockOptionsDescription3: { + id: 'gui.debugModal.thinkAboutBlockOptions.description3', + defaultMessage: 'Try using a similar block in place of what you have, and see if this affects the result.', + description: 'description for the "think about block options" section of the debug modal' + }, + checkTheValuesTitle: { + id: 'gui.debugModal.checkTheValues.title', + defaultMessage: 'Check the Values', + description: 'title for the "check the value" section' + }, + checkTheValuesDescription1: { + id: 'gui.debugModal.checkTheValues.description1', + defaultMessage: + 'If you are using variables or reporter blocks, check the value at the moment the code sequence is run.', + description: 'description for the "check the values" section of the debug modal' + }, + checkTheValuesDescription2: { + id: 'gui.debugModal.checkTheValues.description2', + defaultMessage: 'Do/should all the sprites control a variable, or should only one sprite have control?', + description: 'description for the "check the values" section of the debug modal' + }, + checkTheValuesDescription3: { + id: 'gui.debugModal.checkTheValues.description3', + defaultMessage: 'Where is the value reset? Where is it changed?', + description: 'description for the "check the values" section of the debug modal' + }, + checkCodeSequenceTitle: { + id: 'gui.debugModal.checkCodeSequence.title', + defaultMessage: 'Check Code Sequence', + description: 'title for the "check code sequence" section' + }, + checkCodeSequenceDescription1: { + id: 'gui.debugModal.checkCodeSequence.description1', + defaultMessage: + 'Check that your code sequence is attached to the correct sprite or the backdrop, if appropriate.', + description: 'description for the "check code sequence" section of the debug modal' + }, + checkCodeSequenceDescription2: { + id: 'gui.debugModal.checkCodeSequence.description2', + defaultMessage: + // eslint-disable-next-line max-len + 'If you need to move your code to another sprite, click and drag it until you are hovering over the correct sprite. Release it once the sprite wiggles.', + description: 'description for the "check code sequence" section of the debug modal' + }, + checkCodeSequenceDescription3: { + id: 'gui.debugModal.checkCodeSequence.description3', + defaultMessage: + 'You can also use your Backpack (bottom of screen) to store and move your code or assets.', + description: 'description for the "check code sequence" section of the debug modal' + }, + commentYourCodeTitle: { + id: 'gui.debugModal.commentYourCode.title', + defaultMessage: 'Comment Your Code', + description: 'title for the "comment your code" section' + }, + commentYourCodeDescription1: { + id: 'gui.debugModal.commentYourCode.description1', + defaultMessage: + // eslint-disable-next-line max-len + 'Adding comments to your code can help others looking at your code to understand it. It can also help you remember how your code works when you come back to it later.', + description: 'description for the "comment your code" section of the debug modal' + }, + commentYourCodeDescription2: { + id: 'gui.debugModal.commentYourCode.description2', + defaultMessage: + // eslint-disable-next-line max-len + 'Right click on script area to “Add Comment.” Use everyday language to explain what a block, or small sequence of blocks, does.', + description: 'description for the "comment your code" section of the debug modal' + }, + takeABreakTitle: { + id: 'gui.debugModal.takeABreak.title', + defaultMessage: 'Take a Break, Step Away', + description: 'title for the "take a break" section' + }, + takeABreakDescription1: { + id: 'gui.debugModal.takeABreak.description1', + defaultMessage: + 'Sometimes, spending too much time focused on an issue can be counterproductive and frustrating.', + description: 'description for the "take a break, step away" section of the debug modal' + }, + takeABreakDescription2: { + id: 'gui.debugModal.takeABreak.description2', + defaultMessage: + // eslint-disable-next-line max-len + 'Take a break and step away from the screen to clear your mind. After some rest, focusing on something else, or getting some water, you can approach the problem with fresh eyes.', + description: 'description for the "take a break, step away" section of the debug modal' + }, + askForHelpTitle: { + id: 'gui.debugModal.askForHelp.title', + defaultMessage: 'Ask for Help', + description: 'title for the "ask for help" section' + }, + askForHelpDescription1: { + id: 'gui.debugModal.askForHelp.description1', + defaultMessage: + // eslint-disable-next-line max-len + 'If you are still stuck, you can ask for help from a peer. Try finding a debugging/help studio and share your project, asking for help in a comment or the project notes.', + description: 'description for the "ask for help" section of the debug modal' + }, + askForHelpDescription2: { + id: 'gui.debugModal.askForHelp.description2', + defaultMessage: + // eslint-disable-next-line max-len + 'Ask one to three people to try your code, as different people may have different perspectives or solutions!', + description: 'description for the "ask for help" section of the debug modal' + } +}); diff --git a/src/components/debug-modal/sections/sections.jsx b/src/components/debug-modal/sections/sections.jsx new file mode 100644 index 00000000000..6295ddbabfd --- /dev/null +++ b/src/components/debug-modal/sections/sections.jsx @@ -0,0 +1,144 @@ +import React from 'react'; +import {FormattedMessage} from 'react-intl'; +import {messages} from './messages.ts'; + +import addSoundCheckpoints from '../icons/icon--add-sound-checkpoints.svg'; +import askForHelp from '../icons/icon--ask-for-help.svg'; +import breakItDown from '../icons/icon--break-it-down.svg'; +import checkCodeSequence from '../icons/icon--check-code-sequence.svg'; +import checkTheValues from '../icons/icon--check-the-values.svg'; +import commentYourCode from '../icons/icon--comment-your-code.svg'; +import readAloud from '../icons/icon--read-aloud.svg'; +import slowItDown from '../icons/icon--slow-it-down.svg'; +import takeABreak from '../icons/icon--take-a-break.svg'; +import thinkAboutBlockOptions from '../icons/icon--think-about-block-options.svg'; +import timingAndParallelism from '../icons/icon--timing-and-parallelism.svg'; +import tinkerWithBlockOrder from '../icons/icon--tinker-with-block-order.svg'; +import toLoopOrNotToLoop from '../icons/icon--to-loop-or-not.svg'; + + +export const sections = [ + { + id: 'readAloud', + title: messages.readAloudTitle, + description:
+

+ +
, + image: readAloud + }, { + id: 'breakItDown', + title: messages.breakItDownTitle, + description: (
+

+

+

+
), + image: breakItDown + }, { + id: 'slowItDown', + title: messages.slowItDownTitle, + description: (
+

+

+

+
), + image: slowItDown + }, { + id: 'addSoundCheckpoints', + title: messages.addSoundCheckpointsTitle, + description: (
+

+

+

+
), + image: addSoundCheckpoints + }, { + id: 'tinkerWithBlockOrder', + title: messages.tinkerWithBlockOrderTitle, + description:
+

+ +

+
, + image: tinkerWithBlockOrder + }, { + id: 'toLoopOrNotToLoop', + title: messages.toLoopOrNotTitle, + description: (
+

+

+
), + image: toLoopOrNotToLoop + }, { + id: 'timingAndParallelism', + title: messages.timingAndParallelismTitle, + sectionTitle: messages.timingAndParallelismSectionTitle, + description: (
+

+

+
), + image: timingAndParallelism + }, { + id: 'thinkAboutBlockOptions', + title: messages.thinkAboutBlockOptionsTitle, + description: (
+

+

+

+
), + image: thinkAboutBlockOptions + }, { + id: 'checkTheValues', + title: messages.checkTheValuesTitle, + description:
+

+ +
, + image: checkTheValues + }, { + id: 'checkCodeSequence', + title: messages.checkCodeSequenceTitle, + description:
+

+

+

+
, + image: checkCodeSequence + }, { + id: 'commentYourCode', + title: messages.commentYourCodeTitle, + description:
+

+

+
, + image: commentYourCode + }, { + id: 'takeABreak', + title: messages.takeABreakTitle, + description:
+

+

+
, + image: takeABreak + }, { + id: 'askForHelp', + title: messages.askForHelpTitle, + description:
+

+

+
, + image: askForHelp + } +]; diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx index f48e5a6ec33..f2d0a8e8f73 100644 --- a/src/components/gui/gui.jsx +++ b/src/components/gui/gui.jsx @@ -40,6 +40,7 @@ import addExtensionIcon from './icon--extensions.svg'; import codeIcon from './icon--code.svg'; import costumesIcon from './icon--costumes.svg'; import soundsIcon from './icon--sounds.svg'; +import DebugModal from '../debug-modal/debug-modal.jsx'; const messages = defineMessages({ addExtension: { @@ -82,6 +83,7 @@ const GUIComponent = props => { connectionModalVisible, costumeLibraryVisible, costumesTabVisible, + debugModalVisible, enableCommunity, intl, isCreating, @@ -108,6 +110,7 @@ const GUIComponent = props => { onProjectTelemetryEvent, onRequestCloseBackdropLibrary, onRequestCloseCostumeLibrary, + onRequestCloseDebugModal, onRequestCloseTelemetryModal, onSeeCommunity, onShare, @@ -205,6 +208,10 @@ const GUIComponent = props => { onRequestClose={onRequestCloseCostumeLibrary} /> ) : null} + {} {backdropLibraryVisible ? (
+
+ + + + +
@@ -900,6 +921,7 @@ MenuBar.propTypes = { onLogOut: PropTypes.func, onOpenRegistration: PropTypes.func, onOpenTipLibrary: PropTypes.func, + onOpenDebugModal: PropTypes.func, onProjectTelemetryEvent: PropTypes.func, onRequestCloseAbout: PropTypes.func, onRequestCloseAccount: PropTypes.func, @@ -963,6 +985,7 @@ const mapStateToProps = (state, ownProps) => { const mapDispatchToProps = dispatch => ({ autoUpdateProject: () => dispatch(autoUpdateProject()), onOpenTipLibrary: () => dispatch(openTipsLibrary()), + onOpenDebugModal: () => dispatch(openDebugModal()), onClickAccount: () => dispatch(openAccountMenu()), onRequestCloseAccount: () => dispatch(closeAccountMenu()), onClickFile: () => dispatch(openFileMenu()), diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx index 5ddcb9eed1b..35800f6f43e 100644 --- a/src/containers/gui.jsx +++ b/src/containers/gui.jsx @@ -22,7 +22,8 @@ import { closeCostumeLibrary, closeBackdropLibrary, closeTelemetryModal, - openExtensionLibrary + openExtensionLibrary, + closeDebugModal } from '../reducers/modals'; import FontLoaderHOC from '../lib/font-loader-hoc.jsx'; @@ -158,6 +159,7 @@ const mapStateToProps = state => { connectionModalVisible: state.scratchGui.modals.connectionModal, costumeLibraryVisible: state.scratchGui.modals.costumeLibrary, costumesTabVisible: state.scratchGui.editorTab.activeTabIndex === COSTUMES_TAB_INDEX, + debugModalVisible: state.scratchGui.modals.debugModal, error: state.scratchGui.projectState.error, isError: getIsError(loadingState), isFullScreen: state.scratchGui.mode.isFullScreen, @@ -184,6 +186,7 @@ const mapDispatchToProps = dispatch => ({ onActivateSoundsTab: () => dispatch(activateTab(SOUNDS_TAB_INDEX)), onRequestCloseBackdropLibrary: () => dispatch(closeBackdropLibrary()), onRequestCloseCostumeLibrary: () => dispatch(closeCostumeLibrary()), + onRequestCloseDebugModal: () => dispatch(closeDebugModal()), onRequestCloseTelemetryModal: () => dispatch(closeTelemetryModal()) }); diff --git a/src/css/colors.css b/src/css/colors.css index d5ae6f1a682..538d4e8d032 100644 --- a/src/css/colors.css +++ b/src/css/colors.css @@ -10,6 +10,10 @@ $ui-white-transparent: hsla(0, 100%, 100%, 0.25); /* 25% transparent version of $ui-transparent: hsla(0, 100%, 100%, 0); /* 25% transparent version of ui-white */ $ui-black-transparent: hsla(0, 0%, 0%, 0.15); /* 15% transparent version of black */ +$ui-black-transparent-10: hsla(0, 0%, 0%, 0.10); /* 10% transparent version of black */ + +$ui-green: hsla(163, 85%, 35%, 1); /* #0DA57A */ +$ui-green-2: hsla(163, 85%, 40%, 1); /* #0FBD8C */ $text-primary: hsla(225, 15%, 40%, 1); /* #575E75 */ $text-primary-transparent: hsla(225, 15%, 40%, 0.75); diff --git a/src/lib/analytics.js b/src/lib/analytics.js index d4892d75f03..e0f1d986e7d 100644 --- a/src/lib/analytics.js +++ b/src/lib/analytics.js @@ -2,46 +2,6 @@ // We now use GTM, so we could use `react-gtm-module`, but it doesn't support GTM environments (GTM_ENV_AUTH). // So we use the GTM snippets directly. -const GTM_ID = (process.env.GTM_ID || window.GTM_ID); -const GTM_ENV_AUTH = (process.env.GTM_ENV_AUTH || window.GTM_ENV_AUTH || ''); - -/** - * Build the HTML snippets to load GTM. - * Call this ONLY if GTM_ID is a valid Tag Manager ID. GTM_ENV_AUTH should be valid or an empty string. - * The content of the snippets is taken from the GTM web interface. We should check there periodically for changes. - * @returns {object} an object the GTM snippets. - * @property {string} script The snippet to load GTM when JavaScript is enabled. Add this to the element. - * @property {string} noscript The snippet to load GTM when JavaScript is disabled. Add this to the element. - */ -const makeGtmSnippets = () => ({ - script: - ` - - `, - noscript: - ` - - ` -}); - -if (GTM_ID) { - // load GTM - const snippets = makeGtmSnippets(); - - const noscript = document.createElement('noscript'); - noscript.innerHTML = snippets.noscript; - - const script = document.createElement('script'); - script.innerHTML = snippets.script; - - document.head.insertBefore(script, document.head.firstChild); - document.body.insertBefore(noscript, document.body.firstChild); -} /** * Report analytics to GA4 using an interface similar to the 'react-ga' module we were using for UA. diff --git a/src/playground/index.ejs b/src/playground/index.ejs index b789b004866..abbd5d8b024 100644 --- a/src/playground/index.ejs +++ b/src/playground/index.ejs @@ -1,6 +1,16 @@ + <% if (htmlWebpackPlugin.options.gtm_id) { %> + + + + <% } %> @@ -8,5 +18,10 @@ <%= htmlWebpackPlugin.options.title %> + <% if (htmlWebpackPlugin.options.gtm_id) { %> + + + + <% } %> diff --git a/src/reducers/modals.js b/src/reducers/modals.js index 2e69bf8ba75..5bec3699a1e 100644 --- a/src/reducers/modals.js +++ b/src/reducers/modals.js @@ -3,6 +3,7 @@ const CLOSE_MODAL = 'scratch-gui/modals/CLOSE_MODAL'; const MODAL_BACKDROP_LIBRARY = 'backdropLibrary'; const MODAL_COSTUME_LIBRARY = 'costumeLibrary'; +const MODAL_DEBUG = 'debugModal'; const MODAL_EXTENSION_LIBRARY = 'extensionLibrary'; const MODAL_LOADING_PROJECT = 'loadingProject'; const MODAL_TELEMETRY = 'telemetryModal'; @@ -15,6 +16,7 @@ const MODAL_TIPS_LIBRARY = 'tipsLibrary'; const initialState = { [MODAL_BACKDROP_LIBRARY]: false, [MODAL_COSTUME_LIBRARY]: false, + [MODAL_DEBUG]: false, [MODAL_EXTENSION_LIBRARY]: false, [MODAL_LOADING_PROJECT]: false, [MODAL_TELEMETRY]: false, @@ -58,6 +60,9 @@ const openBackdropLibrary = function () { const openCostumeLibrary = function () { return openModal(MODAL_COSTUME_LIBRARY); }; +const openDebugModal = function () { + return openModal(MODAL_DEBUG); +}; const openExtensionLibrary = function () { return openModal(MODAL_EXTENSION_LIBRARY); }; @@ -88,6 +93,9 @@ const closeBackdropLibrary = function () { const closeCostumeLibrary = function () { return closeModal(MODAL_COSTUME_LIBRARY); }; +const closeDebugModal = function () { + return closeModal(MODAL_DEBUG); +}; const closeExtensionLibrary = function () { return closeModal(MODAL_EXTENSION_LIBRARY); }; @@ -117,6 +125,7 @@ export { initialState as modalsInitialState, openBackdropLibrary, openCostumeLibrary, + openDebugModal, openExtensionLibrary, openLoadingProject, openSoundLibrary, @@ -127,6 +136,7 @@ export { openConnectionModal, closeBackdropLibrary, closeCostumeLibrary, + closeDebugModal, closeExtensionLibrary, closeLoadingProject, closeSpriteLibrary, diff --git a/webpack.config.js b/webpack.config.js index 6813de1187f..1414bd5148c 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -9,6 +9,18 @@ const ScratchWebpackConfigBuilder = require('scratch-webpack-configuration'); // const STATIC_PATH = process.env.STATIC_PATH || '/static'; +const commonHtmlWebpackPluginOptions = { + // Google Tag Manager ID + // Looks like 'GTM-XXXXXXX' + gtm_id: process.env.GTM_ID || '', + + // Google Tag Manager env & auth info for alterative GTM environments + // Looks like '>m_auth=0123456789abcdefghijklm>m_preview=env-00>m_cookies_win=x' + // Taken from the middle of: GTM -> Admin -> Environments -> (environment) -> Get Snippet + // Blank for production + gtm_env_auth: process.env.GTM_ENV_AUTH || '' +}; + const baseConfig = new ScratchWebpackConfigBuilder( { rootPath: path.resolve(__dirname), @@ -108,23 +120,27 @@ const buildConfig = baseConfig.clone() } }) .addPlugin(new HtmlWebpackPlugin({ + ...commonHtmlWebpackPluginOptions, chunks: ['gui'], template: 'src/playground/index.ejs', title: 'Scratch 3.0 GUI' })) .addPlugin(new HtmlWebpackPlugin({ + ...commonHtmlWebpackPluginOptions, chunks: ['blocksonly'], filename: 'blocks-only.html', template: 'src/playground/index.ejs', title: 'Scratch 3.0 GUI: Blocks Only Example' })) .addPlugin(new HtmlWebpackPlugin({ + ...commonHtmlWebpackPluginOptions, chunks: ['compatibilitytesting'], filename: 'compatibility-testing.html', template: 'src/playground/index.ejs', title: 'Scratch 3.0 GUI: Compatibility Testing' })) .addPlugin(new HtmlWebpackPlugin({ + ...commonHtmlWebpackPluginOptions, chunks: ['player'], filename: 'player.html', template: 'src/playground/index.ejs',