diff --git a/apps/scratch/src/blocks/make-toolbox-xml.ts b/apps/scratch/src/blocks/make-toolbox-xml.ts index cdc05b6..b77a6e9 100644 --- a/apps/scratch/src/blocks/make-toolbox-xml.ts +++ b/apps/scratch/src/blocks/make-toolbox-xml.ts @@ -60,14 +60,35 @@ const createDictionaryWithAllValuesInfinite = ( ): Record => Object.values(object).reduce((acc, key) => ({ ...acc, [key]: -1 }), {}); -export const allowAllBlocks: BlockLimits = { - ...createDictionaryWithAllValuesInfinite(MotionOpCode), - ...createDictionaryWithAllValuesInfinite(LooksOpCode), - ...createDictionaryWithAllValuesInfinite(SoundOpCode), - ...createDictionaryWithAllValuesInfinite(EventOpCode), - ...createDictionaryWithAllValuesInfinite(ControlOpCode), - ...createDictionaryWithAllValuesInfinite(SensingOpCode), - ...createDictionaryWithAllValuesInfinite(OperatorsOpCode), +const allowAllMotionBlocks: BlockLimits = + createDictionaryWithAllValuesInfinite(MotionOpCode); + +const allowAllLooksBlocks: BlockLimits = + createDictionaryWithAllValuesInfinite(LooksOpCode); + +const allowAllSoundBlocks: BlockLimits = + createDictionaryWithAllValuesInfinite(SoundOpCode); + +const allowAllEventBlocks: BlockLimits = + createDictionaryWithAllValuesInfinite(EventOpCode); + +const allowAllControlBlocks: BlockLimits = + createDictionaryWithAllValuesInfinite(ControlOpCode); + +const allowAllSensingBlocks: BlockLimits = + createDictionaryWithAllValuesInfinite(SensingOpCode); + +const allowAllOperatorsBlocks: BlockLimits = + createDictionaryWithAllValuesInfinite(OperatorsOpCode); + +export const allowAllStandardBlocks: BlockLimits = { + ...allowAllMotionBlocks, + ...allowAllLooksBlocks, + ...allowAllSoundBlocks, + ...allowAllEventBlocks, + ...allowAllControlBlocks, + ...allowAllSensingBlocks, + ...allowAllOperatorsBlocks, variables: true, customBlocks: true, }; @@ -88,6 +109,7 @@ export const allowNoBlocks: BlockLimits = {}; * @param costumeName - The name of the default selected costume dropdown. * @param backdropName - The name of the default selected backdrop dropdown. * @param soundName - The name of the default selected sound dropdown. + * @param blockLimits - The number of blocks that are allowed to be used for a given task. Null means all blocks are allowed. * @returns- a ScratchBlocks-style XML document for the contents of the toolbox. */ const makeToolboxXML = function ( @@ -99,7 +121,7 @@ const makeToolboxXML = function ( costumeName: string = "", backdropName: string = "", soundName: string = "", - blockLimits: BlockLimits = allowAllBlocks, + blockLimits: BlockLimits | null = null, ): string { isStage = isInitialSetup || isStage; const gap = categorySeparator; @@ -127,7 +149,7 @@ const makeToolboxXML = function ( isStage, targetId, colors.motion, - blockLimits, + blockLimits ?? allowAllMotionBlocks, ); const looksXML = moveCategory("looks") || @@ -138,7 +160,7 @@ const makeToolboxXML = function ( costumeName, backdropName, colors.looks, - blockLimits, + blockLimits ?? allowAllLooksBlocks, ); const soundXML = moveCategory("sound") || @@ -148,11 +170,17 @@ const makeToolboxXML = function ( targetId, soundName, colors.sounds, - blockLimits, + blockLimits ?? allowAllSoundBlocks, ); const eventsXML = moveCategory("event") || - buildEventXml(isInitialSetup, isStage, targetId, colors.event, blockLimits); + buildEventXml( + isInitialSetup, + isStage, + targetId, + colors.event, + blockLimits ?? allowAllEventBlocks, + ); const controlXML = moveCategory("control") || buildControlXml( @@ -160,7 +188,7 @@ const makeToolboxXML = function ( isStage, targetId, colors.control, - blockLimits, + blockLimits ?? allowAllControlBlocks, ); const sensingXML = moveCategory("sensing") || @@ -169,7 +197,7 @@ const makeToolboxXML = function ( isStage, targetId, colors.sensing, - blockLimits, + blockLimits ?? allowAllSensingBlocks, ); const operatorsXML = moveCategory("operators") || @@ -178,7 +206,7 @@ const makeToolboxXML = function ( isStage, targetId, colors.operators, - blockLimits, + blockLimits ?? allowAllOperatorsBlocks, ); const variablesXML = moveCategory("data") || @@ -203,20 +231,46 @@ const makeToolboxXML = function ( gap, operatorsXML, gap, - blockLimits.variables ? variablesXML : "", + blockLimits == null || blockLimits.variables ? variablesXML : "", gap, - blockLimits.customBlocks ? myBlocksXML : "", + blockLimits == null || blockLimits.customBlocks ? myBlocksXML : "", ]; + for (const extensionCategory of categoriesXML) { + if (blockLimits !== null) { + // find all elements and check them against blockLimits + extensionCategory.xml = extensionCategory.xml.replace( + /.*?<\/block>/g, + (match, opcode) => { + // if the opcode is not in any of the blockLimits, return the match + if (opcode in blockLimits) { + const limit = blockLimits[opcode]; + if (limit === 0) { + // if the limit is 0, don't show the block + return ""; + } + } else { + // if the opcode is not in blockLimits, don't show the block + return ""; + } + + return match; + }, + ); + } + + // check if the xml still contains some elements + if (extensionCategory.xml.includes(" xml.includes(" void; }) => { + const assertionsEnabled = useAssertionsEnabled(vm); + const [isAssertionsExtensionEnabled, setIsAssertionsExtensionEnabled] = + useState(false); + + const [enableAssertions, setEnableAssertions] = useState(false); + const updateConfig = useCallback( ( e: React.SyntheticEvent, @@ -37,9 +50,12 @@ const TaskConfig = ({ [vm], ); - const onAllowAllBlocks = useCallback( + const onAllowAllStandardBlocks = useCallback( (e: React.MouseEvent) => { - updateConfig(e, (config) => (config.allowedBlocks = allowAllBlocks)); + updateConfig( + e, + (config) => (config.allowedBlocks = allowAllStandardBlocks), + ); }, [updateConfig], ); @@ -55,32 +71,83 @@ const TaskConfig = ({ e.preventDefault(); e.stopPropagation(); - updateConfig(e, () => {}); + updateConfig(e, () => { + // update assertions + if (isAssertionsExtensionEnabled) { + vm.runtime.emit( + enableAssertions ? "ENABLE_ASSERTIONS" : "DISABLE_ASSERTIONS", + ); + } + }); hideModal(); }, - [vm], + [ + vm, + isAssertionsExtensionEnabled, + enableAssertions, + updateConfig, + hideModal, + ], ); useEffect(() => { // every time the modal is opened, load the form values based on the config + setIsAssertionsExtensionEnabled( + vm.extensionManager.isExtensionLoaded(ExtensionId.Assertions), + ); + + setEnableAssertions(assertionsEnabled); }, [isShown]); return ( -

Task Config

+

+ +

+ {isAssertionsExtensionEnabled && ( + + )} + { + const assertionsEnabled = useAssertionsEnabled(vm); + const assertionsState = useAssertionsState(vm); + + if (!assertionsEnabled) { + return null; + } + + const isSuccessful = + assertionsState.total > 0 && + assertionsState.passed >= assertionsState.total; + + return ( +
+ {assertionsState.total > 0 && ( + + {assertionsState.passed} + {" / "} + {assertionsState.total} + + )} + +
+ ); +}; + +export default AssertionsState; diff --git a/apps/scratch/src/components/assertions-state/assertions-state.css b/apps/scratch/src/components/assertions-state/assertions-state.css new file mode 100644 index 0000000..80dfe72 --- /dev/null +++ b/apps/scratch/src/components/assertions-state/assertions-state.css @@ -0,0 +1,25 @@ +.state { + padding: 0.25rem; + border-radius: 0.25rem; + background-color: var(--assertions-default-color); + color: #fff; + + display: flex; + flex-direction: row; + justify-content: center; + + gap: 0.5rem; + + img { + height: 20px; + width: auto; + } +} + +.success { + background-color: var(--assertions-success-color); +} + +.no-success { + background-color: var(--assertions-default-color); +} diff --git a/apps/scratch/src/components/block-config/BlockConfig.tsx b/apps/scratch/src/components/block-config/BlockConfig.tsx index 0ddc968..ce82b83 100644 --- a/apps/scratch/src/components/block-config/BlockConfig.tsx +++ b/apps/scratch/src/components/block-config/BlockConfig.tsx @@ -4,6 +4,7 @@ import { ModifyBlockConfigEvent } from "../../events/modify-block-config"; import VM from "scratch-vm"; import { UpdateBlockToolboxEvent } from "../../events/update-block-toolbox"; import Modal from "../modal/Modal"; +import { FormattedMessage } from "react-intl"; const cannotBeUsed = 0; const infiniteUses = -1; @@ -96,12 +97,23 @@ const BlockConfig = ({ vm }: { vm: VM }) => { return (

- Block Config ({blockId}) + + ({blockId})