Skip to content
Snippets Groups Projects
target-pane.jsx 11.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • Paul Kaplan's avatar
    Paul Kaplan committed
    import bindAll from 'lodash.bindall';
    import React from 'react';
    
    Ray Schamp's avatar
    Ray Schamp committed
    
    
    Paul Kaplan's avatar
    Paul Kaplan committed
    import {connect} from 'react-redux';
    
    import {intlShape, injectIntl} from 'react-intl';
    
    Ray Schamp's avatar
    Ray Schamp committed
    
    
    Ray Schamp's avatar
    Ray Schamp committed
        openSpriteLibrary,
        closeSpriteLibrary
    
    } from '../reducers/modals';
    
    Ray Schamp's avatar
    Ray Schamp committed
    
    
    import {activateTab, COSTUMES_TAB_INDEX, BLOCKS_TAB_INDEX} from '../reducers/editor-tab';
    
    import {setReceivedBlocks} from '../reducers/hovered-target';
    
    import {setRestore} from '../reducers/restore-deletion';
    
    import DragConstants from '../lib/drag-constants';
    
    Paul Kaplan's avatar
    Paul Kaplan committed
    import TargetPaneComponent from '../components/target-pane/target-pane.jsx';
    
    import spriteLibraryContent from '../lib/libraries/sprites.json';
    
    Karishma Chadha's avatar
    Karishma Chadha committed
    import {handleFileUpload, spriteUpload} from '../lib/file-uploader.js';
    
    import sharedMessages from '../lib/shared-messages';
    import {emptySprite} from '../lib/empty-assets';
    
    import {highlightTarget} from '../reducers/targets';
    
    Ray Schamp's avatar
    Ray Schamp committed
    
    class TargetPane extends React.Component {
        constructor (props) {
            super(props);
            bindAll(this, [
    
                'handleActivateBlocksTab',
    
                'handleChangeSpriteRotationStyle',
    
                'handleChangeSpriteDirection',
    
                'handleChangeSpriteName',
    
                'handleChangeSpriteSize',
    
                'handleChangeSpriteVisibility',
    
    Ray Schamp's avatar
    Ray Schamp committed
                'handleChangeSpriteX',
                'handleChangeSpriteY',
    
                'handleDeleteSprite',
    
                'handleDrop',
    
                'handleDuplicateSprite',
    
                'handleExportSprite',
    
    Karishma Chadha's avatar
    Karishma Chadha committed
                'handleNewSprite',
    
                'handleSelectSprite',
                'handleSurpriseSpriteClick',
    
    Karishma Chadha's avatar
    Karishma Chadha committed
                'handlePaintSpriteClick',
                'handleFileUploadClick',
                'handleSpriteUpload',
                'setFileInput'
    
    Ray Schamp's avatar
    Ray Schamp committed
            ]);
        }
    
        componentDidMount () {
            this.props.vm.addListener('BLOCK_DRAG_END', this.handleBlockDragEnd);
        }
        componentWillUnmount () {
            this.props.vm.removeListener('BLOCK_DRAG_END', this.handleBlockDragEnd);
        }
    
        handleChangeSpriteDirection (direction) {
            this.props.vm.postSpriteInfo({direction});
    
        handleChangeSpriteRotationStyle (rotationStyle) {
            this.props.vm.postSpriteInfo({rotationStyle});
        }
    
        handleChangeSpriteName (name) {
            this.props.vm.renameSprite(this.props.editingTarget, name);
    
        handleChangeSpriteSize (size) {
            this.props.vm.postSpriteInfo({size});
    
        handleChangeSpriteVisibility (visible) {
            this.props.vm.postSpriteInfo({visible});
    
        handleChangeSpriteX (x) {
    
    Ray Schamp's avatar
    Ray Schamp committed
            this.props.vm.postSpriteInfo({x});
        }
    
        handleChangeSpriteY (y) {
    
    Ray Schamp's avatar
    Ray Schamp committed
            this.props.vm.postSpriteInfo({y});
        }
    
        handleDeleteSprite (id) {
    
            const restoreSprite = this.props.vm.deleteSprite(id);
            const restoreFun = () => restoreSprite().then(this.handleActivateBlocksTab);
    
    
            this.props.dispatchUpdateRestore({
                restoreFun: restoreFun,
                deletedItem: 'Sprite'
            });
    
    
        handleDuplicateSprite (id) {
            this.props.vm.duplicateSprite(id);
        }
    
        handleExportSprite (id) {
            const spriteName = this.props.vm.runtime.getTargetById(id).getName();
            const saveLink = document.createElement('a');
            document.body.appendChild(saveLink);
    
            this.props.vm.exportSprite(id).then(content => {
                const filename = `${spriteName}.sprite3`;
    
                // Use special ms version if available to get it working on Edge.
                if (navigator.msSaveOrOpenBlob) {
                    navigator.msSaveOrOpenBlob(content, filename);
                    return;
                }
    
                const url = window.URL.createObjectURL(content);
                saveLink.href = url;
                saveLink.download = filename;
                saveLink.click();
                window.URL.revokeObjectURL(url);
                document.body.removeChild(saveLink);
            });
        }
    
    Ray Schamp's avatar
    Ray Schamp committed
        handleSelectSprite (id) {
            this.props.vm.setEditingTarget(id);
    
            this.props.onHighlightTarget(id, Date.now());
    
    Ray Schamp's avatar
    Ray Schamp committed
        }
    
        handleSurpriseSpriteClick () {
            const item = spriteLibraryContent[Math.floor(Math.random() * spriteLibraryContent.length)];
    
            this.props.vm.addSprite(JSON.stringify(item.json))
                .then(this.handleActivateBlocksTab);
    
        }
        handlePaintSpriteClick () {
    
            const formatMessage = this.props.intl.formatMessage;
            const emptyItem = emptySprite(
    
                formatMessage(sharedMessages.sprite, {index: 1}),
                formatMessage(sharedMessages.pop),
                formatMessage(sharedMessages.costume, {index: 1})
    
            );
            this.props.vm.addSprite(JSON.stringify(emptyItem)).then(() => {
                setTimeout(() => { // Wait for targets update to propagate before tab switching
                    this.props.onActivateTab(COSTUMES_TAB_INDEX);
    
        handleActivateBlocksTab () {
            this.props.onActivateTab(BLOCKS_TAB_INDEX);
        }
    
    Karishma Chadha's avatar
    Karishma Chadha committed
        handleNewSprite (spriteJSONString) {
    
            this.props.vm.addSprite(spriteJSONString)
                .then(this.handleActivateBlocksTab);
    
    Karishma Chadha's avatar
    Karishma Chadha committed
        }
        handleFileUploadClick () {
            this.fileInput.click();
        }
        handleSpriteUpload (e) {
            const storage = this.props.vm.runtime.storage;
    
            const costumeSuffix = this.props.intl.formatMessage(sharedMessages.costume, {index: 1});
    
    Karishma Chadha's avatar
    Karishma Chadha committed
            handleFileUpload(e.target, (buffer, fileType, fileName) => {
    
                spriteUpload(buffer, fileType, fileName, storage, this.handleNewSprite, costumeSuffix);
    
    Karishma Chadha's avatar
    Karishma Chadha committed
            });
        }
        setFileInput (input) {
            this.fileInput = input;
        }
    
        handleBlockDragEnd (blocks) {
            if (this.props.hoveredTarget.sprite && this.props.hoveredTarget.sprite !== this.props.editingTarget) {
    
                this.props.vm.shareBlocksToTarget(blocks, this.props.hoveredTarget.sprite, this.props.editingTarget);
    
                this.props.onReceivedBlocks(true);
            }
        }
    
        handleDrop (dragInfo) {
    
            const {sprite: targetId} = this.props.hoveredTarget;
    
            if (dragInfo.dragType === DragConstants.SPRITE) {
                // Add one to both new and target index because we are not counting/moving the stage
                this.props.vm.reorderTarget(dragInfo.index + 1, dragInfo.newIndex + 1);
    
            } else if (dragInfo.dragType === DragConstants.BACKPACK_SPRITE) {
                // TODO storage does not have a way of loading zips right now, and may never need it.
                // So for now just grab the zip manually.
                fetch(dragInfo.payload.bodyUrl)
                    .then(response => response.arrayBuffer())
    
                    .then(sprite3Zip => this.props.vm.addSprite(sprite3Zip));
    
            } else if (targetId) {
                // Something is being dragged over one of the sprite tiles or the backdrop.
                // Dropping assets like sounds and costumes duplicate the asset on the
                // hovered target. Shared costumes also become the current costume on that target.
                // However, dropping does not switch the editing target or activate that editor tab.
                // This is based on 2.0 behavior, but seems like it keeps confusing switching to a minimum.
                // it allows the user to share multiple things without switching back and forth.
                if (dragInfo.dragType === DragConstants.COSTUME) {
                    this.props.vm.shareCostumeToTarget(dragInfo.index, targetId);
                } else if (targetId && dragInfo.dragType === DragConstants.SOUND) {
                    this.props.vm.shareSoundToTarget(dragInfo.index, targetId);
    
                } else if (dragInfo.dragType === DragConstants.BACKPACK_COSTUME) {
                    // In scratch 2, this only creates a new sprite from the costume.
                    // We may be able to handle both kinds of drops, depending on where
                    // the drop happens. For now, just add the costume.
                    this.props.vm.addCostume(dragInfo.payload.body, {
                        name: dragInfo.payload.name
                    }, targetId);
                } else if (dragInfo.dragType === DragConstants.BACKPACK_SOUND) {
                    this.props.vm.addSound({
                        md5: dragInfo.payload.body,
                        name: dragInfo.payload.name
                    }, targetId);
    
    Ray Schamp's avatar
    Ray Schamp committed
        render () {
    
    Paul Kaplan's avatar
    Paul Kaplan committed
            const {
                onActivateTab, // eslint-disable-line no-unused-vars
    
    DD's avatar
    DD committed
                onReceivedBlocks, // eslint-disable-line no-unused-vars
    
                onHighlightTarget, // eslint-disable-line no-unused-vars
    
    Karishma Chadha's avatar
    Karishma Chadha committed
                dispatchUpdateRestore, // eslint-disable-line no-unused-vars
    
    Paul Kaplan's avatar
    Paul Kaplan committed
                ...componentProps
            } = this.props;
    
    Ray Schamp's avatar
    Ray Schamp committed
            return (
                <TargetPaneComponent
    
    Paul Kaplan's avatar
    Paul Kaplan committed
                    {...componentProps}
    
    Karishma Chadha's avatar
    Karishma Chadha committed
                    fileInputRef={this.setFileInput}
    
                    onChangeSpriteDirection={this.handleChangeSpriteDirection}
    
                    onChangeSpriteName={this.handleChangeSpriteName}
    
    Paul Kaplan's avatar
    Paul Kaplan committed
                    onChangeSpriteRotationStyle={this.handleChangeSpriteRotationStyle}
    
                    onChangeSpriteSize={this.handleChangeSpriteSize}
    
                    onChangeSpriteVisibility={this.handleChangeSpriteVisibility}
    
    Ray Schamp's avatar
    Ray Schamp committed
                    onChangeSpriteX={this.handleChangeSpriteX}
                    onChangeSpriteY={this.handleChangeSpriteY}
    
                    onDeleteSprite={this.handleDeleteSprite}
    
                    onDrop={this.handleDrop}
    
                    onDuplicateSprite={this.handleDuplicateSprite}
    
                    onExportSprite={this.handleExportSprite}
    
    Karishma Chadha's avatar
    Karishma Chadha committed
                    onFileUploadClick={this.handleFileUploadClick}
    
                    onPaintSpriteClick={this.handlePaintSpriteClick}
    
    Ray Schamp's avatar
    Ray Schamp committed
                    onSelectSprite={this.handleSelectSprite}
    
    Karishma Chadha's avatar
    Karishma Chadha committed
                    onSpriteUpload={this.handleSpriteUpload}
    
                    onSurpriseSpriteClick={this.handleSurpriseSpriteClick}
    
    Ray Schamp's avatar
    Ray Schamp committed
                />
            );
        }
    }
    
    const {
        onSelectSprite, // eslint-disable-line no-unused-vars
    
        ...targetPaneProps
    
    Ray Schamp's avatar
    Ray Schamp committed
    } = TargetPaneComponent.propTypes;
    
    TargetPane.propTypes = {
    
        intl: intlShape.isRequired,
    
        ...targetPaneProps
    
    Ray Schamp's avatar
    Ray Schamp committed
    };
    
    const mapStateToProps = state => ({
    
    chrisgarrity's avatar
    chrisgarrity committed
        editingTarget: state.scratchGui.targets.editingTarget,
        hoveredTarget: state.scratchGui.hoveredTarget,
        sprites: Object.keys(state.scratchGui.targets.sprites).reduce((sprites, k) => {
            let {direction, size, x, y, ...sprite} = state.scratchGui.targets.sprites[k];
    
            if (typeof direction !== 'undefined') direction = Math.round(direction);
    
    Ray Schamp's avatar
    Ray Schamp committed
            if (typeof x !== 'undefined') x = Math.round(x);
            if (typeof y !== 'undefined') y = Math.round(y);
    
    Paul Kaplan's avatar
    Paul Kaplan committed
            if (typeof size !== 'undefined') size = Math.round(size);
            sprites[k] = {...sprite, direction, size, x, y};
    
            return sprites;
        }, {}),
    
    chrisgarrity's avatar
    chrisgarrity committed
        stage: state.scratchGui.targets.stage,
        raiseSprites: state.scratchGui.blockDrag,
        spriteLibraryVisible: state.scratchGui.modals.spriteLibrary
    
    Ray Schamp's avatar
    Ray Schamp committed
    });
    const mapDispatchToProps = dispatch => ({
        onNewSpriteClick: e => {
            e.preventDefault();
            dispatch(openSpriteLibrary());
        },
        onRequestCloseSpriteLibrary: () => {
            dispatch(closeSpriteLibrary());
    
    Paul Kaplan's avatar
    Paul Kaplan committed
        },
    
        onActivateTab: tabIndex => {
            dispatch(activateTab(tabIndex));
    
        },
        onReceivedBlocks: receivedBlocks => {
            dispatch(setReceivedBlocks(receivedBlocks));
    
        },
        dispatchUpdateRestore: restoreState => {
            dispatch(setRestore(restoreState));
    
        },
        onHighlightTarget: (id, time) => {
            dispatch(highlightTarget(id, time));
    
    Ray Schamp's avatar
    Ray Schamp committed
        }
    });
    
    
    export default injectIntl(connect(
    
    Ray Schamp's avatar
    Ray Schamp committed
        mapStateToProps,
        mapDispatchToProps