From 95eb548f30f74b665876bdf880b676bcc56f6313 Mon Sep 17 00:00:00 2001 From: Dani McAvoy Date: Tue, 23 Jun 2020 08:26:22 -0400 Subject: [PATCH 1/5] Remove old scratch code --- apps/Gruntfile.js | 26 +- apps/karma.conf.js | 3 +- apps/package.json | 5 - apps/src/scratch/ScratchView.jsx | 34 -- .../scratch/ScratchVisualizationColumn.jsx | 34 -- apps/src/scratch/scratch.js | 194 ----------- apps/src/scratch/scratchDefaultProject.js | 63 ---- .../099ef1e1efb50bd0dc421b0acc6ed8bb.png | Bin 9644 -> 0 bytes .../09dc888b0b7df19f70d81588ae73420e.svg | 42 --- .../3696356a03a8d938318876a593572843.svg | 37 --- .../739b5e2a2435f6e1ec2993791b423146.png | Bin 1264 -> 0 bytes apps/style/scratch/style.scss | 49 --- apps/test-low-memory.sh | 3 - apps/test/scratch-tests.js | 3 - apps/test/scratch/scratchProjectTest.js | 302 ------------------ apps/yarn.lock | 20 -- .../app/controllers/projects_controller.rb | 4 - dashboard/app/models/levels/scratch.rb | 31 -- dashboard/app/views/levels/_scratch.html.haml | 14 - .../config/locales/long_instructions.en.yml | 1 - .../config/locales/short_instructions.en.yml | 1 - .../scripts/levels/New Scratch Project.level | 21 -- .../scripts/levels/test-scratch-level.level | 26 -- lib/cdo/shared_constants.rb | 1 - 24 files changed, 2 insertions(+), 912 deletions(-) delete mode 100644 apps/src/scratch/ScratchView.jsx delete mode 100644 apps/src/scratch/ScratchVisualizationColumn.jsx delete mode 100644 apps/src/scratch/scratch.js delete mode 100644 apps/src/scratch/scratchDefaultProject.js delete mode 100644 apps/static/scratch/099ef1e1efb50bd0dc421b0acc6ed8bb.png delete mode 100644 apps/static/scratch/09dc888b0b7df19f70d81588ae73420e.svg delete mode 100644 apps/static/scratch/3696356a03a8d938318876a593572843.svg delete mode 100644 apps/static/scratch/739b5e2a2435f6e1ec2993791b423146.png delete mode 100644 apps/style/scratch/style.scss delete mode 100644 apps/test/scratch-tests.js delete mode 100644 apps/test/scratch/scratchProjectTest.js delete mode 100644 dashboard/app/models/levels/scratch.rb delete mode 100644 dashboard/app/views/levels/_scratch.html.haml delete mode 100644 dashboard/config/scripts/levels/New Scratch Project.level delete mode 100644 dashboard/config/scripts/levels/test-scratch-level.level diff --git a/apps/Gruntfile.js b/apps/Gruntfile.js index 010f82d29c094..93be8f4b616fc 100644 --- a/apps/Gruntfile.js +++ b/apps/Gruntfile.js @@ -83,7 +83,6 @@ describe('entry tests', () => { 'netsim', 'studio', 'turtle', - 'scratch', 'weblab' ]; @@ -180,12 +179,7 @@ describe('entry tests', () => { src: ['**'], dest: 'build/package/media/skins/fish' }, - { - expand: true, - cwd: 'node_modules/scratch-blocks/media', - src: ['**'], - dest: 'build/package/media/scratch-blocks' - }, + // We have to do some weird stuff to get our fallback video player working. // video.js expects some of its own files to be served by the application, so // we include them in our build and access them via static (non-fingerprinted) @@ -426,12 +420,6 @@ describe('entry tests', () => { included: false, nocache: true }, - { - pattern: 'test/scratch/**/*', - watched: false, - included: false, - nocache: true - }, { pattern: 'test/storybook/**/*', watched: false, @@ -479,15 +467,6 @@ describe('entry tests', () => { }), files: [{src: ['test/integration-tests.js'], watched: false}] }, - scratch: { - coverageIstanbulReporter: { - dir: 'coverage/scratch' - }, - junitReporter: Object.assign({}, junitReporterBaseConfig, { - outputFile: 'scratch.xml' - }), - files: [{src: ['test/scratch-tests.js'], watched: false}] - }, storybook: { coverageIstanbulReporter: { dir: 'coverage/storybook' @@ -1266,8 +1245,5 @@ describe('entry tests', () => { grunt.registerTask('integrationTest', ['preconcat', 'karma:integration']); - // Run Scratch tests in a separate target so `window.Blockly` doesn't collide. - grunt.registerTask('scratchTest', ['preconcat', 'karma:scratch']); - grunt.registerTask('default', ['rebuild', 'test']); }; diff --git a/apps/karma.conf.js b/apps/karma.conf.js index c85ea6d5310af..2a60fea80cb58 100644 --- a/apps/karma.conf.js +++ b/apps/karma.conf.js @@ -49,8 +49,7 @@ module.exports = function(config) { 'test/integration-tests.js': ['webpack', 'sourcemap'], 'test/unit-tests.js': ['webpack'], 'test/code-studio-tests.js': ['webpack', 'sourcemap'], - 'test/storybook-tests.js': ['webpack', 'sourcemap'], - 'test/scratch-tests.js': ['webpack'] + 'test/storybook-tests.js': ['webpack', 'sourcemap'] }, webpack: {...webpackConfig, optimization: undefined, mode: 'development'}, diff --git a/apps/package.json b/apps/package.json index 590328d870fdd..1dd88f6887f64 100644 --- a/apps/package.json +++ b/apps/package.json @@ -196,11 +196,6 @@ "sanitize-html": "^1.11.3", "sass-loader": "6.0.7", "save-csv": "^4.0.6", - "scratch-audio": "v0.1.0-prerelease.1501163867", - "scratch-blocks": " v0.1.0-prerelease.1501101334", - "scratch-render": "v0.1.0-prerelease.1500038694", - "scratch-storage": "^0.2.0", - "scratch-vm": "v0.1.0-prerelease.1501105826-prerelease.1501105849", "script-loader": "^0.7.0", "seedrandom": "2.4.2", "sinon": "^5.0.0", diff --git a/apps/src/scratch/ScratchView.jsx b/apps/src/scratch/ScratchView.jsx deleted file mode 100644 index 7431b06f9a6ec..0000000000000 --- a/apps/src/scratch/ScratchView.jsx +++ /dev/null @@ -1,34 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; - -import StudioAppWrapper from '@cdo/apps/templates/StudioAppWrapper'; -import InstructionsWithWorkspace from '@cdo/apps/templates/instructions/InstructionsWithWorkspace'; -import ScratchVisualizationColumn from './ScratchVisualizationColumn'; -import CodeWorkspace from '@cdo/apps/templates/CodeWorkspace'; -import VisualizationResizeBar from '../lib/ui/VisualizationResizeBar'; - -export default class ScratchView extends React.Component { - static propTypes = { - onMount: PropTypes.func.isRequired - }; - - componentDidMount() { - this.props.onMount(); - } - - render() { - return ( - -
-
- -
- - - - -
-
- ); - } -} diff --git a/apps/src/scratch/ScratchVisualizationColumn.jsx b/apps/src/scratch/ScratchVisualizationColumn.jsx deleted file mode 100644 index 79005232a49a3..0000000000000 --- a/apps/src/scratch/ScratchVisualizationColumn.jsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; -import BelowVisualization from '@cdo/apps/templates/BelowVisualization'; -import ProtectedVisualizationDiv from '@cdo/apps/templates/ProtectedVisualizationDiv'; -import assetUrl from '@cdo/apps/code-studio/assetUrl'; - -const styles = { - scratchStage: { - width: 480, - height: 360 - } -}; - -export default function ScratchVisualizationColumn() { - return ( - - - - - - - - - ); -} diff --git a/apps/src/scratch/scratch.js b/apps/src/scratch/scratch.js deleted file mode 100644 index 05d1bbea0b09a..0000000000000 --- a/apps/src/scratch/scratch.js +++ /dev/null @@ -1,194 +0,0 @@ -import AudioEngine from 'scratch-audio'; -import Renderer from 'scratch-render'; -import Storage from 'scratch-storage'; -import VM from 'scratch-vm'; -import Blockly from 'scratch-blocks'; - -import React from 'react'; -import ReactDOM from 'react-dom'; -import {Provider} from 'react-redux'; - -import {getStore, registerReducers} from '@cdo/apps/redux'; -import * as commonReducers from '@cdo/apps/redux/commonReducers'; -import {singleton as studioApp} from '@cdo/apps/StudioApp'; -import ScratchView from './ScratchView'; -import {scratchDefaultProject} from './scratchDefaultProject'; - -export const __TestInterface = {}; - -export default function init(options) { - registerReducers(commonReducers); - - options.maxVisualizationWidth = 480; - options.vizAspectRatio = 4 / 3; - options.enableShowCode = false; - options.pinWorkspaceToBottom = true; - options.skin = {}; - window.appOptions = options; - - studioApp().configure(options); - studioApp().setPageConstants(options, {}); - - ReactDOM.render( - - studioApp().init(options)} /> - , - document.getElementById(options.containerId) - ); - - /** - * @param {Asset} asset - calculate a URL for this asset. - * @returns {string} a URL to download a project asset (PNG, WAV, etc.) - */ - function getAssetUrl(asset) { - return studioApp().assetUrl( - `media/scratch/${asset.assetId}.${asset.dataFormat}` - ); - } - - // Instantiate the VM. - const vm = new VM(); - __TestInterface.vm = vm; - options.getCode = vm.saveProjectSb3.bind(vm); - - const storage = new Storage(); - const AssetType = storage.AssetType; - storage.addWebSource( - [AssetType.ImageVector, AssetType.ImageBitmap, AssetType.Sound], - getAssetUrl - ); - vm.attachStorage(storage); - - // Instantiate the renderer and connect it to the VM. - const canvas = document.getElementById('scratch-stage'); - - // PhantomJS doesn't support WebGL. - if (!IN_UNIT_TEST) { - const renderer = new Renderer(canvas); - vm.attachRenderer(renderer); - const audioEngine = new AudioEngine(); - vm.attachAudioEngine(audioEngine); - } - - // Load the project. - let project = scratchDefaultProject; - if (options.level.lastAttempt) { - project = options.level.lastAttempt; - } - vm.loadProject(project).then(() => options.onInitialize()); - - // Instantiate scratch-blocks and attach it to the DOM. - const workspace = Blockly.inject('codeWorkspace', { - media: studioApp().assetUrl('media/scratch-blocks/'), - zoom: { - controls: true, - wheel: true, - startScale: 0.75 - }, - colours: { - workspace: '#fff', - flyout: '#ddd', - insertionMarkerOpacity: 0.1 - } - }); - - registerBlockEvents(vm, workspace); - registerInputEvents(vm, canvas); - - // Run threads. - vm.start(); -} - -/** - * Register scratch-blocks events with the VM. - * @param vm - * @param workspace - */ -function registerBlockEvents(vm, workspace) { - workspace.addChangeListener(vm.blockListener); - workspace.addChangeListener(vm.variableListener); - workspace.addChangeListener(() => - dispatchEvent(new Event('workspaceChange')) - ); - const flyoutWorkspace = workspace.getFlyout().getWorkspace(); - flyoutWorkspace.addChangeListener(vm.flyoutBlockListener); - flyoutWorkspace.addChangeListener(vm.monitorBlockListener); - - // Receipt of new block XML for the selected target. - vm.on('workspaceUpdate', data => { - workspace.clear(); - const dom = Blockly.Xml.textToDom(data.xml); - Blockly.Xml.domToWorkspace(dom, workspace); - }); - - // Feedback for stacks and blocks running. - vm.on('SCRIPT_GLOW_ON', data => workspace.glowStack(data.id, true)); - vm.on('SCRIPT_GLOW_OFF', data => workspace.glowStack(data.id, false)); - vm.on('BLOCK_GLOW_ON', data => workspace.glowBlock(data.id, true)); - vm.on('BLOCK_GLOW_OFF', data => workspace.glowBlock(data.id, false)); - vm.on('VISUAL_REPORT', data => workspace.reportValue(data.id, data.value)); -} - -function getCanvasCoordinates(canvas, event) { - const rect = canvas.getBoundingClientRect(); - return { - x: event.clientX - rect.left, - y: event.clientY - rect.top, - canvasWidth: rect.width, - canvasHeight: rect.height - }; -} - -/** - * Register mouse and keyboard events with the VM. - * @param vm - * @param canvas - */ -function registerInputEvents(vm, canvas) { - const greenFlag = document.getElementById('green-flag'); - const stopAll = document.getElementById('stop-all'); - - document.addEventListener('mousemove', e => { - vm.postIOData('mouse', getCanvasCoordinates(canvas, e)); - }); - - canvas.addEventListener('mousedown', e => { - const data = { - isDown: true, - ...getCanvasCoordinates(canvas, e) - }; - vm.postIOData('mouse', data); - e.preventDefault(); - }); - - canvas.addEventListener('mouseup', e => { - const data = { - isDown: false, - ...getCanvasCoordinates(canvas, e) - }; - vm.postIOData('mouse', data); - e.preventDefault(); - }); - - document.addEventListener('keydown', e => { - // Don't capture keys intended for Blockly inputs. - if ([document, document.body, greenFlag, stopAll].includes(e.target)) { - vm.postIOData('keyboard', { - keyCode: e.keyCode, - isDown: true - }); - e.preventDefault(); - } - }); - - document.addEventListener('keyup', e => { - // Always capture up events, even those that have switched to other targets. - vm.postIOData('keyboard', { - keyCode: e.keyCode, - isDown: false - }); - }); - - greenFlag.addEventListener('click', vm.greenFlag.bind(vm)); - stopAll.addEventListener('click', vm.stopAll.bind(vm)); -} diff --git a/apps/src/scratch/scratchDefaultProject.js b/apps/src/scratch/scratchDefaultProject.js deleted file mode 100644 index c7d881e428be0..0000000000000 --- a/apps/src/scratch/scratchDefaultProject.js +++ /dev/null @@ -1,63 +0,0 @@ -export const scratchDefaultProject = JSON.stringify({ - targets: [ - { - name: 'Stage', - isStage: true, - x: 0, - y: 0, - size: 100, - direction: 90, - draggable: false, - currentCostume: 0, - costumeCount: 1, - visible: true, - rotationStyle: 'all around', - blocks: {}, - variables: {}, - lists: {}, - costumes: [ - { - name: 'backdrop1', - bitmapResolution: 1, - rotationCenterX: 240, - rotationCenterY: 180, - skinId: 2, - assetId: '739b5e2a2435f6e1ec2993791b423146', - dataFormat: 'png' - } - ], - sounds: [] - }, - { - name: 'Sprite1', - isStage: false, - x: 0, - y: 0, - size: 100, - direction: 90, - draggable: false, - currentCostume: 0, - costumeCount: 2, - visible: true, - rotationStyle: 'all around', - blocks: {}, - variables: {}, - lists: {}, - costumes: [ - { - name: 'costume1', - bitmapResolution: 1, - rotationCenterX: 47, - rotationCenterY: 55, - skinId: 1, - assetId: '099ef1e1efb50bd0dc421b0acc6ed8bb', - dataFormat: 'png' - } - ], - sounds: [] - } - ], - meta: { - semver: '3.0.0' - } -}); diff --git a/apps/static/scratch/099ef1e1efb50bd0dc421b0acc6ed8bb.png b/apps/static/scratch/099ef1e1efb50bd0dc421b0acc6ed8bb.png deleted file mode 100644 index b02bf1fe58d2541ad21d30a4dad17198dfe38c76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9644 zcmV;dB~#joP);*x*h+R;cqM#s%6s2Bzm7c&PnM@KA0_k<;%-Q?;IIk*E4|M)>J8l!j}DC_rE=n}Y*&Uvm%fIN8+i_i$< z`xKjbrO9N9379+|uq0TzX$ZEB?8-ylzDHIG6zG%0wQ7;y@>l2+IHNPsDajBM(T0_0W&()QYQdj zSV0gU0n8uA)NhPndcBsIFU+g%zF<%MMp|0e{vGnM1lr({)68&4J0{D{qDnj9z{Y)11iO7Pp0 zw=m6=LF_6S5qge*vWv1 z@bJ4d8tosI)zt+`cp8mHLsV##Eg~$UMRa&baAa7R!4Mp+kJZMqv$bd47Nez4Y*g4c zGWhe)59Y!n>D=q|;W!I1vxrLP%F4fQhVCkc7)a(4C-$OB*8W=G4erR%%gWTRuR@0} z)}GrHs@MPCA~trI-EMaQfXQUi5}9OqbP=&8nrGaa!k(P{08{y*Teg?s z(JxAvhw=JwL&Tc~y{4y!@!Kefs%msp)TgyLc65k2QET_yzQ_ajhtjiEUf8ifpnlKM_!)6V7uh>!Eggl(*?>QDgU8 zF%T%QKgS(7<<4W$07lxxAerb?hs|blr+^DYfamnKty)D&qUzlGDKZjn*f>%Kw+E<` zfG`~(%fPJPi2Thq%4C_0&vNE02&C+Q@5WkN66?Mi%d|bMftI8UJM07>Z3)G3XC1T} z6~Gz#x1+efTP-pRf!BW~G6$J@%HfzEC>;Vjrvp$4q^PjsFNvUDg

dn7Yz{$DW^s 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 iwln*e=1}neMf@+}u+kP2T^a=d0000