Merge pull request #7 from tmickel/feature/libraries
Initial rough sprite/costume/backdrop libraries
This commit is contained in:
commit
13efe77bcb
12 files changed
+623
-2
No files matched your search
@@ -45,9 +45,11 @@
|
||||
"opt-cli": "1.5.1",
|
||||
"react": "15.x.x",
|
||||
"react-dom": "15.x.x",
|
||||
"react-modal": "1.5.2",
|
||||
"scratch-blocks": "latest",
|
||||
"scratch-render": "latest",
|
||||
"scratch-vm": "latest",
|
||||
"svg-to-image": "1.1.3",
|
||||
"travis-after-all": "jamesarosen/travis-after-all#override-api-urls",
|
||||
"webpack": "1.13.2",
|
||||
"webpack-dev-server": "1.15.2",
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
const React = require('react');
|
||||
const svgToImage = require('svg-to-image');
|
||||
const xhr = require('xhr');
|
||||
|
||||
/**
|
||||
* @fileoverview
|
||||
* A component for rendering Scratch costume URLs to canvases.
|
||||
* Use for sprite library, costume library, sprite selector, etc.
|
||||
* Props include width, height, and direction (direction in Scratch value).
|
||||
*/
|
||||
|
||||
class CostumeCanvas extends React.Component {
|
||||
componentDidMount () {
|
||||
this.load();
|
||||
}
|
||||
componentDidUpdate (prevProps) {
|
||||
if (prevProps.url !== this.props.url) {
|
||||
this.load();
|
||||
} else {
|
||||
if (prevProps.width !== this.props.width ||
|
||||
prevProps.height !== this.props.height ||
|
||||
prevProps.direction !== this.props.direction) {
|
||||
this.draw();
|
||||
}
|
||||
}
|
||||
}
|
||||
draw () {
|
||||
if (!this.refs.costumeCanvas) {
|
||||
return;
|
||||
}
|
||||
// Draw the costume to the rendered canvas.
|
||||
const img = this.img;
|
||||
const context = this.refs.costumeCanvas.getContext('2d');
|
||||
// Scale to fit.
|
||||
let scale;
|
||||
// Choose the larger dimension to scale by.
|
||||
if (img.width > img.height) {
|
||||
scale = this.refs.costumeCanvas.width / img.width;
|
||||
} else {
|
||||
scale = this.refs.costumeCanvas.height / img.height;
|
||||
}
|
||||
// Rotate by the Scratch-value direction.
|
||||
const angle = (-90 + this.props.direction) * Math.PI / 180;
|
||||
// Rotation origin point will be center of the canvas.
|
||||
const contextTranslateX = this.refs.costumeCanvas.width / 2;
|
||||
const contextTranslateY = this.refs.costumeCanvas.height / 2;
|
||||
// First, clear the canvas.
|
||||
context.clearRect(0, 0,
|
||||
this.refs.costumeCanvas.width, this.refs.costumeCanvas.height);
|
||||
// Translate the context to the center of the canvas,
|
||||
// then rotate canvas drawing by `angle`.
|
||||
context.translate(contextTranslateX, contextTranslateY);
|
||||
context.rotate(angle);
|
||||
context.drawImage(img,
|
||||
0, 0, img.width, img.height,
|
||||
-(scale * img.width / 2), -(scale * img.height / 2),
|
||||
scale * img.width,
|
||||
scale * img.height);
|
||||
// Reset the canvas rotation and translation to 0, (0, 0).
|
||||
context.rotate(-angle);
|
||||
context.translate(-contextTranslateX, -contextTranslateY);
|
||||
}
|
||||
load () {
|
||||
// Draw the icon on our canvas.
|
||||
const url = this.props.url;
|
||||
if (url.indexOf('.svg') > -1) {
|
||||
// Vector graphics: need to download with XDR and rasterize.
|
||||
// Queue request asynchronously.
|
||||
setTimeout(() => {
|
||||
xhr.get({
|
||||
useXDR: true,
|
||||
url: url
|
||||
}, (err, response, body) => {
|
||||
if (!err) {
|
||||
svgToImage(body, (err, img) => {
|
||||
if (!err) {
|
||||
this.img = img;
|
||||
this.draw();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}, 0);
|
||||
|
||||
} else {
|
||||
// Raster graphics: create Image and draw it.
|
||||
let img = new Image();
|
||||
img.src = url;
|
||||
img.onload = () => {
|
||||
this.img = img;
|
||||
this.draw();
|
||||
};
|
||||
}
|
||||
}
|
||||
render () {
|
||||
return <canvas
|
||||
ref='costumeCanvas'
|
||||
width={this.props.width}
|
||||
height={this.props.height}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
CostumeCanvas.defaultProps = {
|
||||
width: 100,
|
||||
height: 100,
|
||||
direction: 90
|
||||
};
|
||||
|
||||
CostumeCanvas.propTypes = {
|
||||
url: React.PropTypes.string,
|
||||
width: React.PropTypes.number,
|
||||
height: React.PropTypes.number,
|
||||
direction: React.PropTypes.number
|
||||
};
|
||||
|
||||
module.exports = CostumeCanvas;
|
||||
@@ -0,0 +1,49 @@
|
||||
const React = require('react');
|
||||
|
||||
const CostumeCanvas = require('./costume-canvas');
|
||||
|
||||
class LibraryItem extends React.Component {
|
||||
render () {
|
||||
let style = (this.props.selected) ?
|
||||
this.props.selectedGridTileStyle : this.props.gridTileStyle;
|
||||
return (
|
||||
<div style={style} onClick={() => this.props.onSelect(this.props.id)}>
|
||||
<CostumeCanvas url={this.props.iconURL} />
|
||||
<p>{this.props.name}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
LibraryItem.defaultProps = {
|
||||
gridTileStyle: {
|
||||
float: 'left',
|
||||
width: '140px',
|
||||
marginLeft: '5px',
|
||||
marginRight: '5px',
|
||||
textAlign: 'center',
|
||||
cursor: 'pointer'
|
||||
},
|
||||
selectedGridTileStyle: {
|
||||
float: 'left',
|
||||
width: '140px',
|
||||
marginLeft: '5px',
|
||||
marginRight: '5px',
|
||||
textAlign: 'center',
|
||||
cursor: 'pointer',
|
||||
background: '#aaa',
|
||||
borderRadius: '6px'
|
||||
}
|
||||
};
|
||||
|
||||
LibraryItem.propTypes = {
|
||||
name: React.PropTypes.string,
|
||||
iconURL: React.PropTypes.string,
|
||||
gridTileStyle: React.PropTypes.object,
|
||||
selectedGridTileStyle: React.PropTypes.object,
|
||||
selected: React.PropTypes.bool,
|
||||
onSelect: React.PropTypes.func,
|
||||
id: React.PropTypes.number
|
||||
};
|
||||
|
||||
module.exports = LibraryItem;
|
||||
@@ -0,0 +1,69 @@
|
||||
const bindAll = require('lodash.bindall');
|
||||
const React = require('react');
|
||||
|
||||
const LibraryItem = require('./library-item');
|
||||
const ModalComponent = require('./modal');
|
||||
|
||||
class LibraryComponent extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, ['onSelect']);
|
||||
this.state = {selectedItem: null};
|
||||
}
|
||||
onSelect (id) {
|
||||
if (this.state.selectedItem == id) {
|
||||
// Double select: select as the library's value.
|
||||
this.props.onRequestClose();
|
||||
this.props.onItemSelected(this.props.data[id]);
|
||||
}
|
||||
this.setState({selectedItem: id});
|
||||
}
|
||||
render () {
|
||||
let itemId = 0;
|
||||
let gridItems = this.props.data.map((dataItem) => {
|
||||
let id = itemId;
|
||||
itemId++;
|
||||
const scratchURL = (dataItem.md5) ? 'https://cdn.assets.scratch.mit.edu/internalapi/asset/' +
|
||||
dataItem.md5 + '/get/' : dataItem.rawURL;
|
||||
return <LibraryItem
|
||||
name={dataItem.name}
|
||||
iconURL={scratchURL}
|
||||
key={'item_' + id}
|
||||
selected={this.state.selectedItem == id}
|
||||
onSelect={this.onSelect}
|
||||
id={id}
|
||||
/>;
|
||||
});
|
||||
|
||||
const scrollGridStyle = {
|
||||
overflow: 'scroll',
|
||||
position: 'absolute',
|
||||
top: '70px',
|
||||
bottom: '20px',
|
||||
left: '30px',
|
||||
right: '30px'
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalComponent
|
||||
onRequestClose={this.props.onRequestClose}
|
||||
visible={this.props.visible}
|
||||
>
|
||||
<h1>{this.props.title}</h1>
|
||||
<div style={scrollGridStyle}>
|
||||
{gridItems}
|
||||
</div>
|
||||
</ModalComponent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
LibraryComponent.propTypes = {
|
||||
title: React.PropTypes.string,
|
||||
data: React.PropTypes.array,
|
||||
visible: React.PropTypes.bool,
|
||||
onRequestClose: React.PropTypes.func,
|
||||
onItemSelected: React.PropTypes.func
|
||||
};
|
||||
|
||||
module.exports = LibraryComponent;
|
||||
@@ -0,0 +1,70 @@
|
||||
const React = require('react');
|
||||
const ReactModal = require('react-modal');
|
||||
|
||||
class ModalComponent extends React.Component {
|
||||
render () {
|
||||
return (
|
||||
<ReactModal
|
||||
ref="modal"
|
||||
style={this.props.modalStyle}
|
||||
isOpen={this.props.visible}
|
||||
onRequestClose={this.props.onRequestClose}
|
||||
>
|
||||
<div
|
||||
onClick={this.props.onRequestClose}
|
||||
style={this.props.closeButtonStyle}
|
||||
>
|
||||
x
|
||||
</div>
|
||||
{this.props.children}
|
||||
</ReactModal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const modalStyle = {
|
||||
overlay: {
|
||||
zIndex: 1000,
|
||||
backgroundColor: 'rgba(0, 0, 0, .75)'
|
||||
},
|
||||
content: {
|
||||
position: 'absolute',
|
||||
overflow: 'visible',
|
||||
borderRadius: '6px',
|
||||
padding: 0,
|
||||
top: '5%',
|
||||
bottom: '5%',
|
||||
left: '5%',
|
||||
right: '5%',
|
||||
background: '#fcfcfc'
|
||||
}
|
||||
};
|
||||
|
||||
const closeButtonStyle = {
|
||||
color: 'rgb(255, 255, 255)',
|
||||
background: 'rgb(50, 50, 50)',
|
||||
borderRadius: '15px',
|
||||
width: '30px',
|
||||
height: '25px',
|
||||
textAlign: 'center',
|
||||
paddingTop: '5px',
|
||||
position: 'absolute',
|
||||
right: '3px',
|
||||
top: '3px',
|
||||
cursor: 'pointer'
|
||||
};
|
||||
|
||||
ModalComponent.defaultProps = {
|
||||
modalStyle: modalStyle,
|
||||
closeButtonStyle: closeButtonStyle
|
||||
};
|
||||
|
||||
ModalComponent.propTypes = {
|
||||
children: React.PropTypes.node,
|
||||
modalStyle: React.PropTypes.object,
|
||||
closeButtonStyle: React.PropTypes.object,
|
||||
onRequestClose: React.PropTypes.func,
|
||||
visible: React.PropTypes.bool
|
||||
};
|
||||
|
||||
module.exports = ModalComponent;
|
||||
@@ -6,6 +6,9 @@ class SpriteSelectorComponent extends React.Component {
|
||||
onChange,
|
||||
sprites,
|
||||
value,
|
||||
openNewSprite,
|
||||
openNewCostume,
|
||||
openNewBackdrop,
|
||||
...props
|
||||
} = this.props;
|
||||
return (
|
||||
@@ -28,6 +31,11 @@ class SpriteSelectorComponent extends React.Component {
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<p>
|
||||
<button onClick={openNewSprite}>New sprite</button>
|
||||
<button onClick={openNewCostume}>New costume</button>
|
||||
<button onClick={openNewBackdrop}>New backdrop</button>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -41,7 +49,10 @@ SpriteSelectorComponent.propTypes = {
|
||||
name: React.PropTypes.string
|
||||
})
|
||||
),
|
||||
value: React.PropTypes.arrayOf(React.PropTypes.string)
|
||||
value: React.PropTypes.arrayOf(React.PropTypes.string),
|
||||
openNewSprite: React.PropTypes.func,
|
||||
openNewCostume: React.PropTypes.func,
|
||||
openNewBackdrop: React.PropTypes.func
|
||||
};
|
||||
|
||||
module.exports = SpriteSelectorComponent;
|
||||
@@ -0,0 +1,53 @@
|
||||
const bindAll = require('lodash.bindall');
|
||||
const React = require('react');
|
||||
const VM = require('scratch-vm');
|
||||
const MediaLibrary = require('../lib/media-library');
|
||||
|
||||
const LibaryComponent = require('../components/library');
|
||||
|
||||
|
||||
class BackdropLibrary extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, ['setData', 'selectItem']);
|
||||
this.state = {backdropData: []};
|
||||
}
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (nextProps.visible && this.state.backdropData.length === 0) {
|
||||
this.props.mediaLibrary.getMediaLibrary('backdrop', this.setData);
|
||||
}
|
||||
}
|
||||
setData (data) {
|
||||
this.setState({backdropData: data});
|
||||
}
|
||||
selectItem (item) {
|
||||
var vmBackdrop = {
|
||||
skin: 'https://cdn.assets.scratch.mit.edu/internalapi/asset/' + item.md5 + '/get/',
|
||||
name: item.name,
|
||||
rotationCenterX: item.info[0],
|
||||
rotationCenterY: item.info[1]
|
||||
};
|
||||
if (item.info.length > 2) {
|
||||
vmBackdrop.bitmapResolution = item.info[2];
|
||||
}
|
||||
this.props.vm.addBackdrop(vmBackdrop);
|
||||
}
|
||||
render () {
|
||||
return <LibaryComponent
|
||||
title="Backdrop Library"
|
||||
visible={this.props.visible}
|
||||
data={this.state.backdropData}
|
||||
onRequestClose={this.props.onRequestClose}
|
||||
onItemSelected={this.selectItem}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
BackdropLibrary.propTypes = {
|
||||
vm: React.PropTypes.instanceOf(VM).isRequired,
|
||||
mediaLibrary: React.PropTypes.instanceOf(MediaLibrary),
|
||||
visible: React.PropTypes.bool,
|
||||
onRequestClose: React.PropTypes.func
|
||||
};
|
||||
|
||||
module.exports = BackdropLibrary;
|
||||
@@ -0,0 +1,53 @@
|
||||
const bindAll = require('lodash.bindall');
|
||||
const React = require('react');
|
||||
const VM = require('scratch-vm');
|
||||
const MediaLibrary = require('../lib/media-library');
|
||||
|
||||
const LibaryComponent = require('../components/library');
|
||||
|
||||
|
||||
class CostumeLibrary extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, ['setData', 'selectItem']);
|
||||
this.state = {costumeData: []};
|
||||
}
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (nextProps.visible && this.state.costumeData.length === 0) {
|
||||
this.props.mediaLibrary.getMediaLibrary('costume', this.setData);
|
||||
}
|
||||
}
|
||||
setData (data) {
|
||||
this.setState({costumeData: data});
|
||||
}
|
||||
selectItem (item) {
|
||||
var vmCostume = {
|
||||
skin: 'https://cdn.assets.scratch.mit.edu/internalapi/asset/' + item.md5 + '/get/',
|
||||
name: item.name,
|
||||
rotationCenterX: item.info[0],
|
||||
rotationCenterY: item.info[1]
|
||||
};
|
||||
if (item.info.length > 2) {
|
||||
vmCostume.bitmapResolution = item.info[2];
|
||||
}
|
||||
this.props.vm.addCostume(vmCostume);
|
||||
}
|
||||
render () {
|
||||
return <LibaryComponent
|
||||
title="Costume Library"
|
||||
visible={this.props.visible}
|
||||
data={this.state.costumeData}
|
||||
onRequestClose={this.props.onRequestClose}
|
||||
onItemSelected={this.selectItem}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
CostumeLibrary.propTypes = {
|
||||
vm: React.PropTypes.instanceOf(VM).isRequired,
|
||||
mediaLibrary: React.PropTypes.instanceOf(MediaLibrary),
|
||||
visible: React.PropTypes.bool,
|
||||
onRequestClose: React.PropTypes.func
|
||||
};
|
||||
|
||||
module.exports = CostumeLibrary;
|
||||
@@ -1,8 +1,10 @@
|
||||
const bindAll = require('lodash.bindall');
|
||||
const defaultsDeep = require('lodash.defaultsdeep');
|
||||
const React = require('react');
|
||||
const VM = require('scratch-vm');
|
||||
|
||||
const VMManager = require('../lib/vm-manager');
|
||||
const MediaLibrary = require('../lib/media-library');
|
||||
|
||||
const Blocks = require('./blocks');
|
||||
const GUIComponent = require('../components/gui');
|
||||
@@ -11,10 +13,17 @@ const SpriteSelector = require('./sprite-selector');
|
||||
const Stage = require('./stage');
|
||||
const StopAll = require('./stop-all');
|
||||
|
||||
const SpriteLibrary = require('./sprite-library');
|
||||
const CostumeLibrary = require('./costume-library');
|
||||
const BackdropLibrary = require('./backdrop-library');
|
||||
|
||||
class GUI extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, ['closeModal']);
|
||||
this.vmManager = new VMManager(this.props.vm);
|
||||
this.mediaLibrary = new MediaLibrary();
|
||||
this.state = {currentModal: null};
|
||||
}
|
||||
componentDidMount () {
|
||||
this.vmManager.attachKeyboardEvents();
|
||||
@@ -30,12 +39,21 @@ class GUI extends React.Component {
|
||||
this.props.vm.loadProject(nextProps.projectData);
|
||||
}
|
||||
}
|
||||
openModal (modalName) {
|
||||
this.setState({currentModal: modalName});
|
||||
}
|
||||
closeModal () {
|
||||
this.setState({currentModal: null});
|
||||
}
|
||||
render () {
|
||||
let {
|
||||
backdropLibraryProps,
|
||||
basePath,
|
||||
blocksProps,
|
||||
costumeLibraryProps,
|
||||
greenFlagProps,
|
||||
projectData, // eslint-disable-line no-unused-vars
|
||||
spriteLibraryProps,
|
||||
spriteSelectorProps,
|
||||
stageProps,
|
||||
stopAllProps,
|
||||
@@ -47,6 +65,26 @@ class GUI extends React.Component {
|
||||
media: basePath + 'static/blocks-media/'
|
||||
}
|
||||
});
|
||||
spriteSelectorProps = defaultsDeep({}, spriteSelectorProps, {
|
||||
openNewBackdrop: () => this.openModal('backdrop-library'),
|
||||
openNewCostume: () => this.openModal('costume-library'),
|
||||
openNewSprite: () => this.openModal('sprite-library')
|
||||
});
|
||||
spriteLibraryProps = defaultsDeep({}, spriteLibraryProps, {
|
||||
mediaLibrary: this.mediaLibrary,
|
||||
onRequestClose: this.closeModal,
|
||||
visible: this.state.currentModal == 'sprite-library'
|
||||
});
|
||||
costumeLibraryProps = defaultsDeep({}, costumeLibraryProps, {
|
||||
mediaLibrary: this.mediaLibrary,
|
||||
onRequestClose: this.closeModal,
|
||||
visible: this.state.currentModal == 'costume-library'
|
||||
});
|
||||
backdropLibraryProps = defaultsDeep({}, backdropLibraryProps, {
|
||||
mediaLibrary: this.mediaLibrary,
|
||||
onRequestClose: this.closeModal,
|
||||
visible: this.state.currentModal == 'backdrop-library'
|
||||
});
|
||||
if (this.props.children) {
|
||||
return (
|
||||
<GUIComponent {... guiProps}>
|
||||
@@ -61,6 +99,9 @@ class GUI extends React.Component {
|
||||
<Stage vm={vm} {...stageProps} />
|
||||
<SpriteSelector vm={vm} {... spriteSelectorProps} />
|
||||
<Blocks vm={vm} {... blocksProps} />
|
||||
<SpriteLibrary vm={vm} {...spriteLibraryProps} />
|
||||
<CostumeLibrary vm={vm} {...costumeLibraryProps} />
|
||||
<BackdropLibrary vm={vm} {...backdropLibraryProps} />
|
||||
</GUIComponent>
|
||||
);
|
||||
}
|
||||
@@ -68,11 +109,14 @@ class GUI extends React.Component {
|
||||
}
|
||||
|
||||
GUI.propTypes = {
|
||||
backdropLibraryProps: React.PropTypes.object,
|
||||
basePath: React.PropTypes.string,
|
||||
blocksProps: React.PropTypes.object,
|
||||
costumeLibraryProps: React.PropTypes.object,
|
||||
children: React.PropTypes.node,
|
||||
greenFlagProps: React.PropTypes.object,
|
||||
projectData: React.PropTypes.string,
|
||||
spriteLibraryProps: React.PropTypes.object,
|
||||
spriteSelectorProps: React.PropTypes.object,
|
||||
stageProps: React.PropTypes.object,
|
||||
stopAllProps: React.PropTypes.object,
|
||||
@@ -80,10 +124,13 @@ GUI.propTypes = {
|
||||
};
|
||||
|
||||
GUI.defaultProps = {
|
||||
backdropLibraryProps: {},
|
||||
basePath: '/',
|
||||
blocksProps: {},
|
||||
costumeLibraryProps: {},
|
||||
greenFlagProps: {},
|
||||
spriteSelectorProps: {},
|
||||
spriteLibraryProps: {},
|
||||
stageProps: {},
|
||||
stopAllProps: {},
|
||||
vm: new VM()
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
const bindAll = require('lodash.bindall');
|
||||
const React = require('react');
|
||||
const VM = require('scratch-vm');
|
||||
const MediaLibrary = require('../lib/media-library');
|
||||
|
||||
const LibaryComponent = require('../components/library');
|
||||
|
||||
class SpriteLibrary extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, ['setData', 'selectItem', 'setSpriteData']);
|
||||
this.state = {data: [], spriteData: {}};
|
||||
}
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (nextProps.visible && this.state.data.length === 0) {
|
||||
this.props.mediaLibrary.getMediaLibrary('sprite', this.setData);
|
||||
}
|
||||
}
|
||||
setData (data) {
|
||||
this.setState({data: data});
|
||||
for (let sprite of data) {
|
||||
this.props.mediaLibrary.getSprite(sprite.md5, this.setSpriteData);
|
||||
}
|
||||
}
|
||||
setSpriteData (md5, data) {
|
||||
let spriteData = this.state.spriteData;
|
||||
spriteData[md5] = data;
|
||||
this.setState({spriteData: spriteData});
|
||||
}
|
||||
selectItem (item) {
|
||||
var spriteData = JSON.stringify(this.state.spriteData[item.json]);
|
||||
this.props.vm.addSprite2(spriteData);
|
||||
}
|
||||
render () {
|
||||
let libraryData = Object.keys(this.state.spriteData).map((libraryKey) => {
|
||||
let libraryItem = this.state.spriteData[libraryKey];
|
||||
return {
|
||||
name: libraryItem.objName,
|
||||
md5: libraryItem.costumes[0].baseLayerMD5,
|
||||
json: libraryKey
|
||||
};
|
||||
});
|
||||
return <LibaryComponent
|
||||
title="Sprite Library"
|
||||
visible={this.props.visible}
|
||||
data={libraryData}
|
||||
mediaLibrary={this.props.mediaLibrary}
|
||||
onRequestClose={this.props.onRequestClose}
|
||||
onItemSelected={this.selectItem}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
SpriteLibrary.propTypes = {
|
||||
vm: React.PropTypes.instanceOf(VM).isRequired,
|
||||
mediaLibrary: React.PropTypes.instanceOf(MediaLibrary),
|
||||
visible: React.PropTypes.bool,
|
||||
onRequestClose: React.PropTypes.func
|
||||
};
|
||||
|
||||
module.exports = SpriteLibrary;
|
||||
@@ -25,12 +25,18 @@ class SpriteSelector extends React.Component {
|
||||
render () {
|
||||
const {
|
||||
vm, // eslint-disable-line no-unused-vars
|
||||
openNewSprite,
|
||||
openNewCostume,
|
||||
openNewBackdrop,
|
||||
...props
|
||||
} = this.props;
|
||||
return (
|
||||
<SpriteSelectorComponent
|
||||
value={this.state.targets.editingTarget && [this.state.targets.editingTarget]}
|
||||
onChange={this.onChange}
|
||||
openNewSprite={openNewSprite}
|
||||
openNewCostume={openNewCostume}
|
||||
openNewBackdrop={openNewBackdrop}
|
||||
sprites={this.state.targets.targetList.map(target => (
|
||||
{
|
||||
id: target[0],
|
||||
@@ -44,7 +50,10 @@ class SpriteSelector extends React.Component {
|
||||
}
|
||||
|
||||
SpriteSelector.propTypes = {
|
||||
vm: React.PropTypes.object.isRequired
|
||||
vm: React.PropTypes.object.isRequired,
|
||||
openNewSprite: React.PropTypes.func,
|
||||
openNewCostume: React.PropTypes.func,
|
||||
openNewBackdrop: React.PropTypes.func
|
||||
};
|
||||
|
||||
module.exports = SpriteSelector;
|
||||
@@ -0,0 +1,80 @@
|
||||
const xhr = require('xhr');
|
||||
|
||||
const LIBRARY_PREFIX = 'https://cdn.scratch.mit.edu/scratchr2/static/' +
|
||||
'__8d9c95eb5aa1272a311775ca32568417__/medialibraries/';
|
||||
const LIBRARY_URL = {
|
||||
sprite: LIBRARY_PREFIX + 'spriteLibrary.json',
|
||||
costume: LIBRARY_PREFIX + 'costumeLibrary.json',
|
||||
backdrop: LIBRARY_PREFIX + 'backdropLibrary.json'
|
||||
};
|
||||
const SPRITE_OBJECT_PREFIX = 'https://cdn.assets.scratch.mit.edu/internalapi/asset/';
|
||||
const SPRITE_OBJECT_SUFFIX = '/get/';
|
||||
|
||||
class MediaLibrary {
|
||||
constructor () {
|
||||
/*
|
||||
* Cached library data, from JSON.
|
||||
* @type {Object}
|
||||
*/
|
||||
this._libraryData = {};
|
||||
|
||||
/**
|
||||
* Cached sprite data, from JSON.
|
||||
* @type {Object.<!string, Object>}
|
||||
*/
|
||||
this._spriteData = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the media library data for a particular scratchr2 library.
|
||||
* In the future, load this from `scratch-storage` asset manager,
|
||||
* e.g., for offline support.
|
||||
* @param {string} libraryType Type of library, i.e., sprite, costume, sound, backdrop.
|
||||
* @param {!Function} callback Callback, called with list of data.
|
||||
*/
|
||||
getMediaLibrary (libraryType, callback) {
|
||||
if (!this._libraryData.hasOwnProperty(libraryType)) {
|
||||
this._libraryData[libraryType] = null;
|
||||
}
|
||||
if (this._libraryData[libraryType]) {
|
||||
callback(this._libraryData[libraryType]);
|
||||
} else {
|
||||
xhr.get({
|
||||
useXDR: true,
|
||||
url: LIBRARY_URL[libraryType]
|
||||
}, (err, response, body) => {
|
||||
if (!err) {
|
||||
let data = JSON.parse(body);
|
||||
this._libraryData[libraryType] = data;
|
||||
callback(this._libraryData[libraryType]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get media library info for a specific scratchr2 sprite.
|
||||
* In the future, load this from `scratch-storage` asset manager,
|
||||
* e.g., for offline support.
|
||||
* @param {string} url URL to sprite (md5.json).
|
||||
* @param {!Function} callback Callback, called with sprite data.
|
||||
*/
|
||||
getSprite (url, callback) {
|
||||
if (this._spriteData.hasOwnProperty(url)) {
|
||||
callback(url, this._spriteData[url]);
|
||||
} else {
|
||||
xhr.get({
|
||||
useXDR: true,
|
||||
url: SPRITE_OBJECT_PREFIX + url + SPRITE_OBJECT_SUFFIX
|
||||
}, (err, response, body) => {
|
||||
if (!err) {
|
||||
let data = JSON.parse(body);
|
||||
this._spriteData[url] = data;
|
||||
callback(url, data);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MediaLibrary;
|
||||
Reference in new issue
Block a user