Skip to content
Snippets Groups Projects
Select Git revision
  • 7d905c2ea05e48ee481b10803fab5ab5bd2a264d
  • develop default
  • gh-pages
  • frontend-logging
  • feature-sb3-save-restore
  • logging
  • default_testing
  • dependabot/npm_and_yarn/style-loader-1.2.1
  • dependabot/npm_and_yarn/eslint-7.5.0
  • dependabot/npm_and_yarn/js-base64-3.2.4
  • master
  • dependabot/npm_and_yarn/chromedriver-84.0.0
  • hotfix/rever-render-version
  • dependabot/npm_and_yarn/scratch-render-0.1.0-prerelease.20200716193608
  • dependabot/npm_and_yarn/jest-26.1.0
  • dependabot/npm_and_yarn/eslint-plugin-jest-23.18.0
  • dependabot/npm_and_yarn/react-ga-3.1.2
  • dependabot/npm_and_yarn/copy-webpack-plugin-6.0.3
  • dependabot/npm_and_yarn/eslint-plugin-react-7.20.3
  • circle-node-testing
  • dependabot/npm_and_yarn/bowser-2.10.0
  • 0.1.0-prerelease.20200720192905
  • 0.1.0-prerelease.20200720192719
  • 0.1.0-prerelease.20200717030138
  • 0.1.0-prerelease.20200716204544
  • 0.1.0-prerelease.20200716195156
  • 0.1.0-prerelease.20200716152052
  • 0.1.0-prerelease.20200716145944
  • 0.1.0-prerelease.20200715032543
  • 0.1.0-prerelease.20200716194917
  • 0.1.0-prerelease.20200714211936
  • 0.1.0-prerelease.20200714210708
  • 0.1.0-prerelease.20200714205751
  • 0.1.0-prerelease.20200714204339
  • 0.1.0-prerelease.20200714165737
  • 0.1.0-prerelease.20200714162202
  • 0.1.0-prerelease.20200714160943
  • 0.1.0-prerelease.20200711060440
  • 0.1.0-prerelease.20200709205146
  • 0.1.0-prerelease.20200709203105
  • 0.1.0-prerelease.20200709155647
41 results

blocks.jsx

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    blocks.jsx 9.13 KiB
    const bindAll = require('lodash.bindall');
    const debounce = require('lodash.debounce');
    const defaultsDeep = require('lodash.defaultsdeep');
    const PropTypes = require('prop-types');
    const React = require('react');
    const VMScratchBlocks = require('../lib/blocks');
    const VM = require('scratch-vm');
    const Prompt = require('./prompt.jsx');
    const BlocksComponent = require('../components/blocks/blocks.jsx');
    
    const addFunctionListener = (object, property, callback) => {
        const oldFn = object[property];
        object[property] = function () {
            const result = oldFn.apply(this, arguments);
            callback.apply(this, result);
            return result;
        };
    };
    
    class Blocks extends React.Component {
        constructor (props) {
            super(props);
            this.ScratchBlocks = VMScratchBlocks(props.vm);
            bindAll(this, [
                'attachVM',
                'detachVM',
                'handlePromptStart',
                'handlePromptCallback',
                'handlePromptClose',
                'onScriptGlowOn',
                'onScriptGlowOff',
                'onBlockGlowOn',
                'onBlockGlowOff',
                'onTargetsUpdate',
                'onVisualReport',
                'onWorkspaceUpdate',
                'onWorkspaceMetricsChange',
                'setBlocks'
            ]);
            this.ScratchBlocks.prompt = this.handlePromptStart;
            this.state = {
                workspaceMetrics: {},
                prompt: null
            };
            this.onTargetsUpdate = debounce(this.onTargetsUpdate, 100);
        }
        componentDidMount () {
            const workspaceConfig = defaultsDeep({}, Blocks.defaultOptions, this.props.options);
            this.workspace = this.ScratchBlocks.inject(this.blocks, workspaceConfig);
    
            // @todo change this when blockly supports UI events
            addFunctionListener(this.workspace, 'translate', this.onWorkspaceMetricsChange);
            addFunctionListener(this.workspace, 'zoom', this.onWorkspaceMetricsChange);
    
            this.attachVM();
        }
        shouldComponentUpdate (nextProps, nextState) {
            return this.state.prompt !== nextState.prompt || this.props.isVisible !== nextProps.isVisible;
        }
        componentDidUpdate (prevProps) {
            if (this.props.isVisible === prevProps.isVisible) {
                return;
            }
    
            // @todo hack to resize blockly manually in case resize happened while hidden
            if (this.props.isVisible) { // Scripts tab
                window.dispatchEvent(new Event('resize'));
                this.workspace.setVisible(true);
                this.workspace.toolbox_.refreshSelection();
            } else {
                this.workspace.setVisible(false);
            }
        }
        componentWillUnmount () {
            this.detachVM();
            this.workspace.dispose();
        }
        attachVM () {
            this.workspace.addChangeListener(this.props.vm.blockListener);
            this.flyoutWorkspace = this.workspace
                .getFlyout()
                .getWorkspace();
            this.flyoutWorkspace.addChangeListener(this.props.vm.flyoutBlockListener);
            this.flyoutWorkspace.addChangeListener(this.props.vm.monitorBlockListener);
            this.props.vm.addListener('SCRIPT_GLOW_ON', this.onScriptGlowOn);
            this.props.vm.addListener('SCRIPT_GLOW_OFF', this.onScriptGlowOff);
            this.props.vm.addListener('BLOCK_GLOW_ON', this.onBlockGlowOn);
            this.props.vm.addListener('BLOCK_GLOW_OFF', this.onBlockGlowOff);
            this.props.vm.addListener('VISUAL_REPORT', this.onVisualReport);
            this.props.vm.addListener('workspaceUpdate', this.onWorkspaceUpdate);
            this.props.vm.addListener('targetsUpdate', this.onTargetsUpdate);
        }
        detachVM () {
            this.props.vm.removeListener('SCRIPT_GLOW_ON', this.onScriptGlowOn);
            this.props.vm.removeListener('SCRIPT_GLOW_OFF', this.onScriptGlowOff);
            this.props.vm.removeListener('BLOCK_GLOW_ON', this.onBlockGlowOn);
            this.props.vm.removeListener('BLOCK_GLOW_OFF', this.onBlockGlowOff);
            this.props.vm.removeListener('VISUAL_REPORT', this.onVisualReport);
            this.props.vm.removeListener('workspaceUpdate', this.onWorkspaceUpdate);
            this.props.vm.removeListener('targetsUpdate', this.onTargetsUpdate);
        }
        updateToolboxBlockValue (id, value) {
            this.workspace
                .getFlyout()
                .getWorkspace()
                .getBlockById(id)
                .inputList[0]
                .fieldRow[0]
                .setValue(value);
        }
        onTargetsUpdate () {
            if (this.props.vm.editingTarget) {
                ['glide', 'move', 'set'].forEach(prefix => {
                    this.updateToolboxBlockValue(`${prefix}x`, this.props.vm.editingTarget.x.toFixed(0));
                    this.updateToolboxBlockValue(`${prefix}y`, this.props.vm.editingTarget.y.toFixed(0));
                });
            }
        }
        onWorkspaceMetricsChange () {
            const target = this.props.vm.editingTarget;
            if (target && target.id) {
                const workspaceMetrics = Object.assign({}, this.state.workspaceMetrics, {
                    [target.id]: {
                        scrollX: this.workspace.scrollX,
                        scrollY: this.workspace.scrollY,
                        scale: this.workspace.scale
                    }
                });
                this.setState({workspaceMetrics});
            }
        }
        onScriptGlowOn (data) {
            this.workspace.glowStack(data.id, true);
        }
        onScriptGlowOff (data) {
            this.workspace.glowStack(data.id, false);
        }
        onBlockGlowOn (data) {
            this.workspace.glowBlock(data.id, true);
        }
        onBlockGlowOff (data) {
            this.workspace.glowBlock(data.id, false);
        }
        onVisualReport (data) {
            this.workspace.reportValue(data.id, data.value);
        }
        onWorkspaceUpdate (data) {
            if (this.props.vm.editingTarget && !this.state.workspaceMetrics[this.props.vm.editingTarget.id]) {
                this.onWorkspaceMetricsChange();
            }
    
            this.ScratchBlocks.Events.disable();
            this.workspace.clear();
    
            const dom = this.ScratchBlocks.Xml.textToDom(data.xml);
            this.ScratchBlocks.Xml.domToWorkspace(dom, this.workspace);
            this.ScratchBlocks.Events.enable();
            this.workspace.toolbox_.refreshSelection();
    
            if (this.props.vm.editingTarget && this.state.workspaceMetrics[this.props.vm.editingTarget.id]) {
                const {scrollX, scrollY, scale} = this.state.workspaceMetrics[this.props.vm.editingTarget.id];
                this.workspace.scrollX = scrollX;
                this.workspace.scrollY = scrollY;
                this.workspace.scale = scale;
                this.workspace.resize();
            }
        }
        setBlocks (blocks) {
            this.blocks = blocks;
        }
        handlePromptStart (message, defaultValue, callback) {
            this.setState({prompt: {callback, message, defaultValue}});
        }
        handlePromptCallback (data) {
            this.state.prompt.callback(data);
            this.handlePromptClose();
        }
        handlePromptClose () {
            this.setState({prompt: null});
        }
        render () {
            const {
                options, // eslint-disable-line no-unused-vars
                vm, // eslint-disable-line no-unused-vars
                isVisible, // eslint-disable-line no-unused-vars
                ...props
            } = this.props;
            return (
                <div>
                    <BlocksComponent
                        componentRef={this.setBlocks}
                        {...props}
                    />
                    {this.state.prompt ? (
                        <Prompt
                            label={this.state.prompt.message}
                            placeholder={this.state.prompt.defaultValue}
                            title="New Variable" // @todo the only prompt is for new variables
                            onCancel={this.handlePromptClose}
                            onOk={this.handlePromptCallback}
                        />
                    ) : null}
                </div>
            );
        }
    }
    
    Blocks.propTypes = {
        isVisible: PropTypes.bool.isRequired,
        options: PropTypes.shape({
            media: PropTypes.string,
            zoom: PropTypes.shape({
                controls: PropTypes.bool,
                wheel: PropTypes.bool,
                startScale: PropTypes.number
            }),
            colours: PropTypes.shape({
                workspace: PropTypes.string,
                flyout: PropTypes.string,
                toolbox: PropTypes.string,
                toolboxSelected: PropTypes.string,
                scrollbar: PropTypes.string,
                scrollbarHover: PropTypes.string,
                insertionMarker: PropTypes.string,
                insertionMarkerOpacity: PropTypes.number,
                fieldShadow: PropTypes.string,
                dragShadowOpacity: PropTypes.number
            })
        }),
        vm: PropTypes.instanceOf(VM).isRequired
    };
    
    Blocks.defaultOptions = {
        zoom: {
            controls: true,
            wheel: true,
            startScale: 0.75
        },
        grid: {
            spacing: 40,
            length: 2,
            colour: '#ddd'
        },
        colours: {
            workspace: '#F9F9F9',
            flyout: '#F9F9F9',
            toolbox: '#FFFFFF',
            toolboxSelected: '#E9EEF2',
            scrollbar: '#CECDCE',
            scrollbarHover: '#CECDCE',
            insertionMarker: '#000000',
            insertionMarkerOpacity: 0.2,
            fieldShadow: 'rgba(255, 255, 255, 0.3)',
            dragShadowOpacity: 0.6
        }
    };
    
    Blocks.defaultProps = {
        options: Blocks.defaultOptions
    };
    
    module.exports = Blocks;