Import and export bitmaps ()

* Rename svg to image

* Import/export bitmap

* Fix playground and readme

* Fix lint

* Assume strings are svgs
This commit is contained in:
DD Liu 2018-04-26 18:45:50 -04:00 committed by GitHub
parent af29e606d8
commit 3d99044ccf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 350 additions and 301 deletions

View file

@ -60,17 +60,22 @@ Then go to [http://localhost:8601](http://localhost:8601). 601 is supposed to lo
### How to include in your own Node.js App
For an example of how to use scratch-paint as a library, check out the `scratch-paint/src/playground` directory.
In `playground.jsx`, you can change the SVG vector that is passed in, and edit the handler `onUpdateSvg`, which is called with the new SVG each time the vector drawing is edited.
In `playground.jsx`, you can change the image that is passed in (which may either be nothing, an SVG string or an HTMLImageElement) and edit the handler `onUpdateImage`, which is called with the new image (either an SVG string or an HTMLCanvasElement) each time the vector drawing is edited.
If the `imageId` parameter changes, then the paint editor will be cleared, the undo stack reset, and the image re-imported.
SVGs of up to size 480 x 360 will fit into the view window of the paint editor, while bitmaps of size up to 960 x 720 will fit into the paint editor. One unit of an SVG will appear twice as tall and wide as one unit of a bitmap. This quirky import behavior comes from needing to support legacy projects in Scratch.
In your parent component:
```
import PaintEditor from 'scratch-paint';
...
<PaintEditor
svg={optionalSvg}
rotationCenterX={optionalCenterPointXRelativeToSvgTopLeft}
rotationCenterY={optionalCenterPointYRelativeToSvgTopLeft}
onUpdateSvg={handleUpdateSvgFunction}
image={optionalImage}
imageId={optionalId}
rotationCenterX={optionalCenterPointXRelativeToTopLeft}
rotationCenterY={optionalCenterPointYRelativeToTopLeft}
onUpdateImage={handleUpdateImageFunction}
/>
```

View file

@ -329,23 +329,24 @@ const PaintEditorComponent = props => {
{/* fill */}
<FillColorIndicatorComponent
className={styles.modMarginRight}
onUpdateSvg={props.onUpdateSvg}
onUpdateImage={props.onUpdateImage}
/>
{/* stroke */}
<StrokeColorIndicatorComponent
onUpdateSvg={props.onUpdateSvg}
onUpdateImage={props.onUpdateImage}
/>
{/* stroke width */}
<StrokeWidthIndicatorComponent
onUpdateSvg={props.onUpdateSvg}
onUpdateImage={props.onUpdateImage}
/>
</InputGroup>
<InputGroup className={styles.modModeTools}>
<ModeToolsContainer
onUpdateSvg={props.onUpdateSvg}
onUpdateImage={props.onUpdateImage}
/>
</InputGroup>
</div> :
isBitmap(props.format) ?
<div className={styles.row}>
<InputGroup
className={classNames(
@ -357,15 +358,15 @@ const PaintEditorComponent = props => {
{/* fill */}
<FillColorIndicatorComponent
className={styles.modMarginRight}
onUpdateSvg={props.onUpdateSvg}
onUpdateImage={props.onUpdateImage}
/>
</InputGroup>
<InputGroup className={styles.modModeTools}>
<ModeToolsContainer
onUpdateSvg={props.onUpdateSvg}
onUpdateImage={props.onUpdateImage}
/>
</InputGroup>
</div>
</div> : null
}
</div>
) : null}
@ -375,32 +376,32 @@ const PaintEditorComponent = props => {
{props.canvas !== null ? ( // eslint-disable-line no-negated-condition
<div className={isVector(props.format) ? styles.modeSelector : styles.hidden}>
<SelectMode
onUpdateSvg={props.onUpdateSvg}
onUpdateImage={props.onUpdateImage}
/>
<ReshapeMode
onUpdateSvg={props.onUpdateSvg}
onUpdateImage={props.onUpdateImage}
/>
<BrushMode
onUpdateSvg={props.onUpdateSvg}
onUpdateImage={props.onUpdateImage}
/>
<EraserMode
onUpdateSvg={props.onUpdateSvg}
onUpdateImage={props.onUpdateImage}
/>
<FillMode
onUpdateSvg={props.onUpdateSvg}
onUpdateImage={props.onUpdateImage}
/>
<TextMode
textArea={props.textArea}
onUpdateSvg={props.onUpdateSvg}
onUpdateImage={props.onUpdateImage}
/>
<LineMode
onUpdateSvg={props.onUpdateSvg}
onUpdateImage={props.onUpdateImage}
/>
<OvalMode
onUpdateSvg={props.onUpdateSvg}
onUpdateImage={props.onUpdateImage}
/>
<RectMode
onUpdateSvg={props.onUpdateSvg}
onUpdateImage={props.onUpdateImage}
/>
</div>
) : null}
@ -408,10 +409,10 @@ const PaintEditorComponent = props => {
{props.canvas !== null ? ( // eslint-disable-line no-negated-condition
<div className={isBitmap(props.format) ? styles.modeSelector : styles.hidden}>
<BitBrushMode
onUpdateSvg={props.onUpdateSvg}
onUpdateImage={props.onUpdateImage}
/>
<BitLineMode
onUpdateSvg={props.onUpdateSvg}
onUpdateImage={props.onUpdateImage}
/>
<BitOvalMode />
<BitRectMode />
@ -432,11 +433,11 @@ const PaintEditorComponent = props => {
>
<PaperCanvas
canvasRef={props.setCanvas}
image={props.image}
imageId={props.imageId}
rotationCenterX={props.rotationCenterX}
rotationCenterY={props.rotationCenterY}
svg={props.svg}
svgId={props.svgId}
onUpdateSvg={props.onUpdateSvg}
onUpdateImage={props.onUpdateImage}
/>
<textarea
className={styles.textArea}
@ -470,6 +471,7 @@ const PaintEditorComponent = props => {
{props.intl.formatMessage(messages.bitmap)}
</span>
</Button> :
isBitmap(props.format) ?
<Button
className={styles.bitmapButton}
onClick={props.onSwitchToVector}
@ -482,7 +484,7 @@ const PaintEditorComponent = props => {
<span>
{props.intl.formatMessage(messages.vector)}
</span>
</Button>
</Button> : null
}
{/* Zoom controls */}
<InputGroup className={styles.zoomControls}>
@ -534,7 +536,12 @@ PaintEditorComponent.propTypes = {
canUndo: PropTypes.func.isRequired,
canvas: PropTypes.instanceOf(Element),
colorInfo: Loupe.propTypes.colorInfo,
format: PropTypes.oneOf(Object.keys(Formats)).isRequired,
format: PropTypes.oneOf(Object.keys(Formats)),
image: PropTypes.oneOfType([
PropTypes.string,
PropTypes.instanceOf(HTMLImageElement)
]),
imageId: PropTypes.string,
intl: intlShape,
isEyeDropping: PropTypes.bool,
name: PropTypes.string,
@ -548,8 +555,8 @@ PaintEditorComponent.propTypes = {
onSwitchToVector: PropTypes.func.isRequired,
onUndo: PropTypes.func.isRequired,
onUngroup: PropTypes.func.isRequired,
onUpdateImage: PropTypes.func.isRequired,
onUpdateName: PropTypes.func.isRequired,
onUpdateSvg: PropTypes.func.isRequired,
onZoomIn: PropTypes.func.isRequired,
onZoomOut: PropTypes.func.isRequired,
onZoomReset: PropTypes.func.isRequired,
@ -557,8 +564,6 @@ PaintEditorComponent.propTypes = {
rotationCenterY: PropTypes.number,
setCanvas: PropTypes.func.isRequired,
setTextArea: PropTypes.func.isRequired,
svg: PropTypes.string,
svgId: PropTypes.string,
textArea: PropTypes.instanceOf(Element)
};

View file

@ -52,7 +52,7 @@ class BitBrushMode extends React.Component {
color = DEFAULT_COLOR;
}
this.tool = new BitBrushTool(
this.props.onUpdateSvg
this.props.onUpdateImage
);
this.tool.setColor(color);
this.tool.setBrushSize(this.props.bitBrushSize);
@ -81,7 +81,7 @@ BitBrushMode.propTypes = {
handleMouseDown: PropTypes.func.isRequired,
isBitBrushModeActive: PropTypes.bool.isRequired,
onChangeFillColor: PropTypes.func.isRequired,
onUpdateSvg: PropTypes.func.isRequired
onUpdateImage: PropTypes.func.isRequired
};
const mapStateToProps = state => ({

View file

@ -52,7 +52,7 @@ class BitLineMode extends React.Component {
color = DEFAULT_COLOR;
}
this.tool = new BitLineTool(
this.props.onUpdateSvg
this.props.onUpdateImage
);
this.tool.setColor(color);
this.tool.setLineSize(this.props.bitBrushSize);
@ -81,7 +81,7 @@ BitLineMode.propTypes = {
handleMouseDown: PropTypes.func.isRequired,
isBitLineModeActive: PropTypes.bool.isRequired,
onChangeFillColor: PropTypes.func.isRequired,
onUpdateSvg: PropTypes.func.isRequired
onUpdateImage: PropTypes.func.isRequired
};
const mapStateToProps = state => ({

View file

@ -21,7 +21,7 @@ class BrushMode extends React.Component {
'deactivateTool'
]);
this.blob = new Blobbiness(
this.props.onUpdateSvg, this.props.clearSelectedItems);
this.props.onUpdateImage, this.props.clearSelectedItems);
}
componentDidMount () {
if (this.props.isBrushModeActive) {
@ -85,7 +85,7 @@ BrushMode.propTypes = {
handleMouseDown: PropTypes.func.isRequired,
isBrushModeActive: PropTypes.bool.isRequired,
onChangeFillColor: PropTypes.func.isRequired,
onUpdateSvg: PropTypes.func.isRequired
onUpdateImage: PropTypes.func.isRequired
};
const mapStateToProps = state => ({

View file

@ -17,7 +17,7 @@ class EraserMode extends React.Component {
'deactivateTool'
]);
this.blob = new Blobbiness(
this.props.onUpdateSvg, this.props.clearSelectedItems);
this.props.onUpdateImage, this.props.clearSelectedItems);
}
componentDidMount () {
if (this.props.isEraserModeActive) {
@ -62,7 +62,7 @@ EraserMode.propTypes = {
}),
handleMouseDown: PropTypes.func.isRequired,
isEraserModeActive: PropTypes.bool.isRequired,
onUpdateSvg: PropTypes.func.isRequired
onUpdateImage: PropTypes.func.isRequired
};
const mapStateToProps = state => ({

View file

@ -21,10 +21,10 @@ class FillColorIndicator extends React.Component {
this._hasChanged = false;
}
componentWillReceiveProps (newProps) {
const {fillColorModalVisible, onUpdateSvg} = this.props;
const {fillColorModalVisible, onUpdateImage} = this.props;
if (fillColorModalVisible && !newProps.fillColorModalVisible) {
// Submit the new SVG, which also stores a single undo/redo action.
if (this._hasChanged) onUpdateSvg();
if (this._hasChanged) onUpdateImage();
this._hasChanged = false;
}
}
@ -77,7 +77,7 @@ FillColorIndicator.propTypes = {
isEyeDropping: PropTypes.bool.isRequired,
onChangeFillColor: PropTypes.func.isRequired,
onCloseFillColor: PropTypes.func.isRequired,
onUpdateSvg: PropTypes.func.isRequired,
onUpdateImage: PropTypes.func.isRequired,
textEditTarget: PropTypes.number
};

View file

@ -53,7 +53,7 @@ class FillMode extends React.Component {
this.tool = new FillTool(
this.props.setHoveredItem,
this.props.clearHoveredItem,
this.props.onUpdateSvg
this.props.onUpdateImage
);
this.tool.setFillColor(this.props.fillColor === MIXED ? DEFAULT_COLOR : this.props.fillColor);
this.tool.setPrevHoveredItemId(this.props.hoveredItemId);
@ -82,7 +82,7 @@ FillMode.propTypes = {
hoveredItemId: PropTypes.number,
isFillModeActive: PropTypes.bool.isRequired,
onChangeFillColor: PropTypes.func.isRequired,
onUpdateSvg: PropTypes.func.isRequired,
onUpdateImage: PropTypes.func.isRequired,
setHoveredItem: PropTypes.func.isRequired
};

View file

@ -220,7 +220,7 @@ class LineMode extends React.Component {
}
if (this.path) {
this.props.onUpdateSvg();
this.props.onUpdateImage();
this.path = null;
}
this.active = false;
@ -257,7 +257,7 @@ LineMode.propTypes = {
isLineModeActive: PropTypes.bool.isRequired,
onChangeStrokeColor: PropTypes.func.isRequired,
onChangeStrokeWidth: PropTypes.func.isRequired,
onUpdateSvg: PropTypes.func.isRequired
onUpdateImage: PropTypes.func.isRequired
};
const mapStateToProps = state => ({

View file

@ -117,7 +117,7 @@ class ModeTools extends React.Component {
}
if (changed) {
this.props.setSelectedItems();
this.props.onUpdateSvg();
this.props.onUpdateImage();
}
}
handlePointPoints () {
@ -133,7 +133,7 @@ class ModeTools extends React.Component {
}
if (changed) {
this.props.setSelectedItems();
this.props.onUpdateSvg();
this.props.onUpdateImage();
}
}
_handleFlip (horizontalScale, verticalScale) {
@ -160,7 +160,7 @@ class ModeTools extends React.Component {
}
itemGroup.remove();
this.props.onUpdateSvg();
this.props.onUpdateImage();
}
handleFlipHorizontal () {
this._handleFlip(-1, 1);
@ -194,7 +194,7 @@ class ModeTools extends React.Component {
}
this.props.incrementPasteOffset();
this.props.setSelectedItems();
this.props.onUpdateSvg();
this.props.onUpdateImage();
}
}
render () {
@ -217,7 +217,7 @@ ModeTools.propTypes = {
clearSelectedItems: PropTypes.func.isRequired,
clipboardItems: PropTypes.arrayOf(PropTypes.array),
incrementPasteOffset: PropTypes.func.isRequired,
onUpdateSvg: PropTypes.func.isRequired,
onUpdateImage: PropTypes.func.isRequired,
pasteOffset: PropTypes.number,
// Listen on selected items to update hasSelectedPoints
selectedItems:

View file

@ -65,7 +65,7 @@ class OvalMode extends React.Component {
this.tool = new OvalTool(
this.props.setSelectedItems,
this.props.clearSelectedItems,
this.props.onUpdateSvg
this.props.onUpdateImage
);
this.tool.setColorState(this.props.colorState);
this.tool.activate();
@ -96,7 +96,7 @@ OvalMode.propTypes = {
isOvalModeActive: PropTypes.bool.isRequired,
onChangeFillColor: PropTypes.func.isRequired,
onChangeStrokeColor: PropTypes.func.isRequired,
onUpdateSvg: PropTypes.func.isRequired,
onUpdateImage: PropTypes.func.isRequired,
selectedItems: PropTypes.arrayOf(PropTypes.instanceOf(paper.Item)),
setSelectedItems: PropTypes.func.isRequired
};

View file

@ -35,7 +35,7 @@ class PaintEditor extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleUpdateSvg',
'handleUpdateImage',
'handleUndo',
'handleRedo',
'handleSendBackward',
@ -118,7 +118,7 @@ class PaintEditor extends React.Component {
}
}
}
handleUpdateSvg (skipSnapshot) {
handleUpdateImage (skipSnapshot) {
// Store the zoom/pan and restore it after snapshotting
// TODO Only doing this because snapshotting at zoom/pan makes export wrong
const oldZoom = paper.project.view.zoom;
@ -127,22 +127,23 @@ class PaintEditor extends React.Component {
let raster;
if (isBitmap(this.props.format)) {
// @todo export bitmap here
raster = trim(getRaster());
if (raster.width === 0 || raster.height === 0) {
raster.remove();
} else {
paper.project.activeLayer.addChild(raster);
}
}
this.props.onUpdateImage(
false /* isVector */,
raster.canvas,
paper.project.view.center.x - raster.bounds.x,
paper.project.view.center.y - raster.bounds.y);
} else if (isVector(this.props.format)) {
const guideLayers = hideGuideLayers(true /* includeRaster */);
// Export at 0.5x
scaleWithStrokes(paper.project.activeLayer, .5, new paper.Point());
const bounds = paper.project.activeLayer.bounds;
this.props.onUpdateSvg(
this.props.onUpdateImage(
true /* isVector */,
paper.project.exportSVG({
asString: true,
bounds: 'content',
@ -155,7 +156,7 @@ class PaintEditor extends React.Component {
paper.project.activeLayer.applyMatrix = true;
showGuideLayers(guideLayers);
if (raster) raster.remove();
}
if (!skipSnapshot) {
performSnapshot(this.props.undoSnapshot, this.props.format);
@ -166,28 +167,28 @@ class PaintEditor extends React.Component {
paper.project.view.center = oldCenter;
}
handleUndo () {
performUndo(this.props.undoState, this.props.onUndo, this.props.setSelectedItems, this.handleUpdateSvg);
performUndo(this.props.undoState, this.props.onUndo, this.props.setSelectedItems, this.handleUpdateImage);
}
handleRedo () {
performRedo(this.props.undoState, this.props.onRedo, this.props.setSelectedItems, this.handleUpdateSvg);
performRedo(this.props.undoState, this.props.onRedo, this.props.setSelectedItems, this.handleUpdateImage);
}
handleGroup () {
groupSelection(this.props.clearSelectedItems, this.props.setSelectedItems, this.handleUpdateSvg);
groupSelection(this.props.clearSelectedItems, this.props.setSelectedItems, this.handleUpdateImage);
}
handleUngroup () {
ungroupSelection(this.props.clearSelectedItems, this.props.setSelectedItems, this.handleUpdateSvg);
ungroupSelection(this.props.clearSelectedItems, this.props.setSelectedItems, this.handleUpdateImage);
}
handleSendBackward () {
sendBackward(this.handleUpdateSvg);
sendBackward(this.handleUpdateImage);
}
handleSendForward () {
bringForward(this.handleUpdateSvg);
bringForward(this.handleUpdateImage);
}
handleSendToBack () {
sendToBack(this.handleUpdateSvg);
sendToBack(this.handleUpdateImage);
}
handleSendToFront () {
bringToFront(this.handleUpdateSvg);
bringToFront(this.handleUpdateImage);
}
canUndo () {
return shouldShowUndo(this.props.undoState);
@ -287,14 +288,14 @@ class PaintEditor extends React.Component {
canvas={this.state.canvas}
colorInfo={this.state.colorInfo}
format={this.props.format}
image={this.props.image}
imageId={this.props.imageId}
isEyeDropping={this.props.isEyeDropping}
name={this.props.name}
rotationCenterX={this.props.rotationCenterX}
rotationCenterY={this.props.rotationCenterY}
setCanvas={this.setCanvas}
setTextArea={this.setTextArea}
svg={this.props.svg}
svgId={this.props.svgId}
textArea={this.state.textArea}
onGroup={this.handleGroup}
onRedo={this.handleRedo}
@ -306,8 +307,8 @@ class PaintEditor extends React.Component {
onSwitchToVector={this.props.handleSwitchToVector}
onUndo={this.handleUndo}
onUngroup={this.handleUngroup}
onUpdateImage={this.handleUpdateImage}
onUpdateName={this.props.onUpdateName}
onUpdateSvg={this.handleUpdateSvg}
onZoomIn={this.handleZoomIn}
onZoomOut={this.handleZoomOut}
onZoomReset={this.handleZoomReset}
@ -320,9 +321,14 @@ PaintEditor.propTypes = {
changeColorToEyeDropper: PropTypes.func,
changeMode: PropTypes.func.isRequired,
clearSelectedItems: PropTypes.func.isRequired,
format: PropTypes.oneOf(Object.keys(Formats)).isRequired,
format: PropTypes.oneOf(Object.keys(Formats)),
handleSwitchToBitmap: PropTypes.func.isRequired,
handleSwitchToVector: PropTypes.func.isRequired,
image: PropTypes.oneOfType([
PropTypes.string,
PropTypes.instanceOf(HTMLImageElement)
]),
imageId: PropTypes.string,
isEyeDropping: PropTypes.bool,
mode: PropTypes.oneOf(Object.keys(Modes)).isRequired,
name: PropTypes.string,
@ -330,8 +336,8 @@ PaintEditor.propTypes = {
onKeyPress: PropTypes.func.isRequired,
onRedo: PropTypes.func.isRequired,
onUndo: PropTypes.func.isRequired,
onUpdateImage: PropTypes.func.isRequired,
onUpdateName: PropTypes.func.isRequired,
onUpdateSvg: PropTypes.func.isRequired,
previousTool: PropTypes.shape({ // paper.Tool
activate: PropTypes.func.isRequired,
remove: PropTypes.func.isRequired
@ -340,8 +346,6 @@ PaintEditor.propTypes = {
rotationCenterX: PropTypes.number,
rotationCenterY: PropTypes.number,
setSelectedItems: PropTypes.func.isRequired,
svg: PropTypes.string,
svgId: PropTypes.string,
textEditing: PropTypes.bool.isRequired,
undoSnapshot: PropTypes.func.isRequired,
undoState: PropTypes.shape({

View file

@ -28,6 +28,7 @@ class PaperCanvas extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'checkFormat',
'convertToBitmap',
'convertToVector',
'setCanvas',
@ -51,15 +52,26 @@ class PaperCanvas extends React.Component {
paper.settings.handleSize = 0;
// Make layers.
setupLayers();
if (this.props.svg) {
this.importSvg(this.props.svg, this.props.rotationCenterX, this.props.rotationCenterY);
if (this.props.image) {
if (isBitmap(this.checkFormat(this.props.image))) {
// import bitmap
this.props.changeFormat(Formats.BITMAP_SKIP_CONVERT);
performSnapshot(this.props.undoSnapshot, this.props.format);
getRaster().drawImage(
this.props.image,
paper.project.view.center.x - this.props.rotationCenterX,
paper.project.view.center.y - this.props.rotationCenterY);
} else if (isVector(this.checkFormat(this.props.image))) {
this.props.changeFormat(Formats.VECTOR_SKIP_CONVERT);
this.importSvg(this.props.image, this.props.rotationCenterX, this.props.rotationCenterY);
}
} else {
performSnapshot(this.props.undoSnapshot, this.props.format);
}
}
componentWillReceiveProps (newProps) {
if (this.props.svgId !== newProps.svgId) {
this.switchCostume(newProps.svg, newProps.rotationCenterX, newProps.rotationCenterY);
if (this.props.imageId !== newProps.imageId) {
this.switchCostume(newProps.image, newProps.rotationCenterX, newProps.rotationCenterY);
} else if (isVector(this.props.format) && newProps.format === Formats.BITMAP) {
this.convertToBitmap();
} else if (isBitmap(this.props.format) && newProps.format === Formats.VECTOR) {
@ -77,7 +89,7 @@ class PaperCanvas extends React.Component {
}
// Backspace, delete
if (event.key === 'Delete' || event.key === 'Backspace') {
if (deleteSelection(this.props.mode, this.props.onUpdateSvg)) {
if (deleteSelection(this.props.mode, this.props.onUpdateImage)) {
this.props.setSelectedItems();
}
}
@ -109,13 +121,14 @@ class PaperCanvas extends React.Component {
const img = new Image();
img.onload = () => {
const raster = new paper.Raster(img);
raster.remove();
raster.onLoad = () => {
const subCanvas = raster.canvas;
getRaster().drawImage(
subCanvas,
new paper.Point(Math.floor(bounds.topLeft.x), Math.floor(bounds.topLeft.y)));
paper.project.activeLayer.removeChildren();
this.props.onUpdateSvg();
this.props.onUpdateImage();
};
};
img.src = `data:image/svg+xml;charset=utf-8,${svgString}`;
@ -133,9 +146,15 @@ class PaperCanvas extends React.Component {
paper.project.activeLayer.addChild(raster);
}
clearRaster();
this.props.onUpdateSvg();
this.props.onUpdateImage();
}
switchCostume (svg, rotationCenterX, rotationCenterY) {
checkFormat (image) {
if (image instanceof HTMLImageElement) return Formats.BITMAP;
if (typeof image === 'string') return Formats.VECTOR;
log.error(`Image could not be read.`);
return null;
}
switchCostume (image, rotationCenterX, rotationCenterY) {
for (const layer of paper.project.layers) {
if (layer.data.isRasterLayer) {
clearRaster();
@ -147,16 +166,29 @@ class PaperCanvas extends React.Component {
this.props.clearSelectedItems();
this.props.clearHoveredItem();
this.props.clearPasteOffset();
if (svg) {
if (image) {
if (isBitmap(this.checkFormat(image))) {
// import bitmap
this.props.changeFormat(Formats.BITMAP_SKIP_CONVERT);
getRaster().drawImage(
image,
paper.project.view.center.x - rotationCenterX,
paper.project.view.center.y - rotationCenterY);
performSnapshot(this.props.undoSnapshot, this.props.format);
} else if (isVector(this.checkFormat(image))) {
this.props.changeFormat(Formats.VECTOR_SKIP_CONVERT);
// Store the zoom/pan and restore it after importing a new SVG
const oldZoom = paper.project.view.zoom;
const oldCenter = paper.project.view.center.clone();
resetZoom();
this.props.updateViewBounds(paper.view.matrix);
this.importSvg(svg, rotationCenterX, rotationCenterY);
this.importSvg(image, rotationCenterX, rotationCenterY);
paper.project.view.zoom = oldZoom;
paper.project.view.center = oldCenter;
} else {
log.error(`Couldn't open image.`);
performSnapshot(this.props.undoSnapshot, this.props.format);
}
} else {
performSnapshot(this.props.undoSnapshot, this.props.format);
}
@ -290,14 +322,17 @@ PaperCanvas.propTypes = {
clearPasteOffset: PropTypes.func.isRequired,
clearSelectedItems: PropTypes.func.isRequired,
clearUndo: PropTypes.func.isRequired,
format: PropTypes.oneOf(Object.keys(Formats)).isRequired,
format: PropTypes.oneOf(Object.keys(Formats)),
image: PropTypes.oneOfType([
PropTypes.string,
PropTypes.instanceOf(HTMLImageElement)
]),
imageId: PropTypes.string,
mode: PropTypes.oneOf(Object.keys(Modes)),
onUpdateSvg: PropTypes.func.isRequired,
onUpdateImage: PropTypes.func.isRequired,
rotationCenterX: PropTypes.number,
rotationCenterY: PropTypes.number,
setSelectedItems: PropTypes.func.isRequired,
svg: PropTypes.string,
svgId: PropTypes.string,
undoSnapshot: PropTypes.func.isRequired,
updateViewBounds: PropTypes.func.isRequired
};

View file

@ -65,7 +65,7 @@ class RectMode extends React.Component {
this.tool = new RectTool(
this.props.setSelectedItems,
this.props.clearSelectedItems,
this.props.onUpdateSvg
this.props.onUpdateImage
);
this.tool.setColorState(this.props.colorState);
this.tool.activate();
@ -96,7 +96,7 @@ RectMode.propTypes = {
isRectModeActive: PropTypes.bool.isRequired,
onChangeFillColor: PropTypes.func.isRequired,
onChangeStrokeColor: PropTypes.func.isRequired,
onUpdateSvg: PropTypes.func.isRequired,
onUpdateImage: PropTypes.func.isRequired,
selectedItems: PropTypes.arrayOf(PropTypes.instanceOf(paper.Item)),
setSelectedItems: PropTypes.func.isRequired
};

View file

@ -45,7 +45,7 @@ class ReshapeMode extends React.Component {
this.props.clearHoveredItem,
this.props.setSelectedItems,
this.props.clearSelectedItems,
this.props.onUpdateSvg
this.props.onUpdateImage
);
this.tool.setPrevHoveredItemId(this.props.hoveredItemId);
this.tool.activate();
@ -72,7 +72,7 @@ ReshapeMode.propTypes = {
handleMouseDown: PropTypes.func.isRequired,
hoveredItemId: PropTypes.number,
isReshapeModeActive: PropTypes.bool.isRequired,
onUpdateSvg: PropTypes.func.isRequired,
onUpdateImage: PropTypes.func.isRequired,
setHoveredItem: PropTypes.func.isRequired,
setSelectedItems: PropTypes.func.isRequired
};

View file

@ -45,7 +45,7 @@ class RoundedRectMode extends React.Component {
this.props.clearHoveredItem,
this.props.setSelectedItems,
this.props.clearSelectedItems,
this.props.onUpdateSvg
this.props.onUpdateImage
);
this.tool.activate();
}
@ -70,7 +70,7 @@ RoundedRectMode.propTypes = {
handleMouseDown: PropTypes.func.isRequired,
hoveredItemId: PropTypes.number,
isRoundedRectModeActive: PropTypes.bool.isRequired,
onUpdateSvg: PropTypes.func.isRequired,
onUpdateImage: PropTypes.func.isRequired,
setHoveredItem: PropTypes.func.isRequired,
setSelectedItems: PropTypes.func.isRequired
};

View file

@ -49,7 +49,7 @@ class SelectMode extends React.Component {
this.props.clearHoveredItem,
this.props.setSelectedItems,
this.props.clearSelectedItems,
this.props.onUpdateSvg
this.props.onUpdateImage
);
this.tool.activate();
}
@ -74,7 +74,7 @@ SelectMode.propTypes = {
handleMouseDown: PropTypes.func.isRequired,
hoveredItemId: PropTypes.number,
isSelectModeActive: PropTypes.bool.isRequired,
onUpdateSvg: PropTypes.func.isRequired,
onUpdateImage: PropTypes.func.isRequired,
selectedItems: PropTypes.arrayOf(PropTypes.instanceOf(paper.Item)),
setHoveredItem: PropTypes.func.isRequired,
setSelectedItems: PropTypes.func.isRequired

View file

@ -21,10 +21,10 @@ class StrokeColorIndicator extends React.Component {
this._hasChanged = false;
}
componentWillReceiveProps (newProps) {
const {strokeColorModalVisible, onUpdateSvg} = this.props;
const {strokeColorModalVisible, onUpdateImage} = this.props;
if (strokeColorModalVisible && !newProps.strokeColorModalVisible) {
// Submit the new SVG, which also stores a single undo/redo action.
if (this._hasChanged) onUpdateSvg();
if (this._hasChanged) onUpdateImage();
this._hasChanged = false;
}
}
@ -76,7 +76,7 @@ StrokeColorIndicator.propTypes = {
isEyeDropping: PropTypes.bool.isRequired,
onChangeStrokeColor: PropTypes.func.isRequired,
onCloseStrokeColor: PropTypes.func.isRequired,
onUpdateSvg: PropTypes.func.isRequired,
onUpdateImage: PropTypes.func.isRequired,
strokeColor: PropTypes.string,
strokeColorModalVisible: PropTypes.bool.isRequired,
textEditTarget: PropTypes.number

View file

@ -16,7 +16,7 @@ class StrokeWidthIndicator extends React.Component {
}
handleChangeStrokeWidth (newWidth) {
if (applyStrokeWidthToSelection(newWidth, this.props.textEditTarget)) {
this.props.onUpdateSvg();
this.props.onUpdateImage();
}
this.props.onChangeStrokeWidth(newWidth);
}
@ -46,7 +46,7 @@ const mapDispatchToProps = dispatch => ({
StrokeWidthIndicator.propTypes = {
disabled: PropTypes.bool.isRequired,
onChangeStrokeWidth: PropTypes.func.isRequired,
onUpdateSvg: PropTypes.func.isRequired,
onUpdateImage: PropTypes.func.isRequired,
strokeWidth: PropTypes.number,
textEditTarget: PropTypes.number
};

View file

@ -73,7 +73,7 @@ class TextMode extends React.Component {
this.props.textArea,
this.props.setSelectedItems,
this.props.clearSelectedItems,
this.props.onUpdateSvg,
this.props.onUpdateImage,
this.props.setTextEditTarget,
);
this.tool.setColorState(this.props.colorState);
@ -105,7 +105,7 @@ TextMode.propTypes = {
isTextModeActive: PropTypes.bool.isRequired,
onChangeFillColor: PropTypes.func.isRequired,
onChangeStrokeColor: PropTypes.func.isRequired,
onUpdateSvg: PropTypes.func.isRequired,
onUpdateImage: PropTypes.func.isRequired,
selectedItems: PropTypes.arrayOf(PropTypes.instanceOf(paper.Item)),
setSelectedItems: PropTypes.func.isRequired,
setTextEditTarget: PropTypes.func.isRequired,

View file

@ -8,11 +8,11 @@ import {getGuideLayer} from '../layer';
*/
class BrushTool extends paper.Tool {
/**
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
* @param {!function} onUpdateImage A callback to call when the image visibly changes
*/
constructor (onUpdateSvg) {
constructor (onUpdateImage) {
super();
this.onUpdateSvg = onUpdateSvg;
this.onUpdateImage = onUpdateImage;
// We have to set these functions instead of just declaring them because
// paper.js tools hook up the listeners in the setter functions.
@ -87,7 +87,7 @@ class BrushTool extends paper.Tool {
if (event.event.button > 0 || !this.active) return; // only first mouse button
forEachLinePoint(this.lastPoint, event.point, this.draw.bind(this));
this.onUpdateSvg();
this.onUpdateImage();
this.lastPoint = null;
this.active = false;

View file

@ -9,11 +9,11 @@ import {ART_BOARD_WIDTH, ART_BOARD_HEIGHT} from '../view';
*/
class LineTool extends paper.Tool {
/**
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
* @param {!function} onUpdateImage A callback to call when the image visibly changes
*/
constructor (onUpdateSvg) {
constructor (onUpdateImage) {
super();
this.onUpdateSvg = onUpdateSvg;
this.onUpdateImage = onUpdateImage;
// We have to set these functions instead of just declaring them because
// paper.js tools hook up the listeners in the setter functions.
@ -103,7 +103,7 @@ class LineTool extends paper.Tool {
this.drawTarget = getRaster();
forEachLinePoint(this.startPoint, event.point, this.draw.bind(this));
this.drawTarget = null;
this.onUpdateSvg();
this.onUpdateImage();
this.lastPoint = null;
this.active = false;

View file

@ -27,13 +27,13 @@ class Blobbiness {
}
/**
* @param {function} onUpdateSvg call when the drawing has changed to let listeners know
* @param {function} onUpdateImage call when the drawing has changed to let listeners know
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
*/
constructor (onUpdateSvg, clearSelectedItems) {
constructor (onUpdateImage, clearSelectedItems) {
this.broadBrushHelper = new BroadBrushHelper();
this.segmentBrushHelper = new SegmentBrushHelper();
this.onUpdateSvg = onUpdateSvg;
this.onUpdateImage = onUpdateImage;
this.clearSelectedItems = clearSelectedItems;
// The following are stored to check whether these have changed and the cursor preview needs to be redrawn.
@ -143,7 +143,7 @@ class Blobbiness {
// Remove cursor preview during snapshot, then bring it back
blob.cursorPreview.remove();
blob.onUpdateSvg();
blob.onUpdateImage();
blob.cursorPreview.parent = getGuideLayer();
// Reset

View file

@ -11,10 +11,10 @@ const isGroup = function (item) {
* @param {!Array<paper.Item>} items Root level items to group
* @param {!function} clearSelectedItems Function to clear Redux state's selected items
* @param {!function} setSelectedItems Function to set Redux state with new list of selected items
* @param {!function} onUpdateSvg Function to let listeners know that SVG has changed.
* @param {!function} onUpdateImage Function to let listeners know that SVG has changed.
* @return {paper.Group} the group if one is created, otherwise false.
*/
const groupItems = function (items, clearSelectedItems, setSelectedItems, onUpdateSvg) {
const groupItems = function (items, clearSelectedItems, setSelectedItems, onUpdateImage) {
if (items.length > 0) {
const group = new paper.Group(items);
clearSelection(clearSelectedItems);
@ -23,7 +23,7 @@ const groupItems = function (items, clearSelectedItems, setSelectedItems, onUpda
group.children[i].selected = true;
}
setSelectedItems();
onUpdateSvg();
onUpdateImage();
return group;
}
return false;
@ -33,12 +33,12 @@ const groupItems = function (items, clearSelectedItems, setSelectedItems, onUpda
* Groups the selected items. Other things are then deselected and the new group is selected.
* @param {!function} clearSelectedItems Function to clear Redux state's selected items
* @param {!function} setSelectedItems Function to set Redux state with new list of selected items
* @param {!function} onUpdateSvg Function to let listeners know that SVG has changed.
* @param {!function} onUpdateImage Function to let listeners know that SVG has changed.
* @return {paper.Group} the group if one is created, otherwise false.
*/
const groupSelection = function (clearSelectedItems, setSelectedItems, onUpdateSvg) {
const groupSelection = function (clearSelectedItems, setSelectedItems, onUpdateImage) {
const items = getSelectedRootItems();
return groupItems(items, clearSelectedItems, setSelectedItems, onUpdateSvg);
return groupItems(items, clearSelectedItems, setSelectedItems, onUpdateImage);
};
const _ungroupLoop = function (group, recursive, setSelectedItems) {
@ -71,15 +71,15 @@ const _ungroupLoop = function (group, recursive, setSelectedItems) {
/**
* Ungroups the given items. The new group is selected only if setSelectedItems is passed in.
* onUpdateSvg is called to notify listeners of a change on the SVG only if onUpdateSvg is passed in.
* onUpdateImage is called to notify listeners of a change on the SVG only if onUpdateImage is passed in.
* The reason these arguments are optional on ungroupItems is because ungroupItems is used for parts of
* SVG import, which shouldn't change the selection or undo state.
*
* @param {!Array<paper.Item>} items Items to ungroup if they are groups
* @param {?function} setSelectedItems Function to set Redux state with new list of selected items
* @param {?function} onUpdateSvg Function to let listeners know that SVG has changed.
* @param {?function} onUpdateImage Function to let listeners know that SVG has changed.
*/
const ungroupItems = function (items, setSelectedItems, onUpdateSvg) {
const ungroupItems = function (items, setSelectedItems, onUpdateImage) {
if (items.length === 0) {
return;
}
@ -102,8 +102,8 @@ const ungroupItems = function (items, setSelectedItems, onUpdateSvg) {
emptyGroups[j].remove();
}
// @todo: enable/disable grouping icons
if (onUpdateSvg) {
onUpdateSvg();
if (onUpdateImage) {
onUpdateImage();
}
};
@ -112,12 +112,12 @@ const ungroupItems = function (items, setSelectedItems, onUpdateSvg) {
*
* @param {!function} clearSelectedItems Function to clear Redux state's selected items
* @param {!function} setSelectedItems Function to set Redux state with new list of selected items
* @param {!function} onUpdateSvg Function to let listeners know that SVG has changed.
* @param {!function} onUpdateImage Function to let listeners know that SVG has changed.
*/
const ungroupSelection = function (clearSelectedItems, setSelectedItems, onUpdateSvg) {
const ungroupSelection = function (clearSelectedItems, setSelectedItems, onUpdateImage) {
const items = getSelectedRootItems();
clearSelection(clearSelectedItems);
ungroupItems(items, setSelectedItems, onUpdateSvg);
ungroupItems(items, setSelectedItems, onUpdateImage);
};
const getItemsGroup = function (item) {

View file

@ -1,22 +1,22 @@
import {getSelectedRootItems} from './selection';
const bringToFront = function (onUpdateSvg) {
const bringToFront = function (onUpdateImage) {
const items = getSelectedRootItems();
for (const item of items) {
item.bringToFront();
}
onUpdateSvg();
onUpdateImage();
};
const sendToBack = function (onUpdateSvg) {
const sendToBack = function (onUpdateImage) {
const items = getSelectedRootItems();
for (let i = items.length - 1; i >= 0; i--) {
items[i].sendToBack();
}
onUpdateSvg();
onUpdateImage();
};
const bringForward = function (onUpdateSvg) {
const bringForward = function (onUpdateImage) {
const items = getSelectedRootItems();
// Already at front
if (items.length === 0 || !items[items.length - 1].nextSibling) {
@ -27,10 +27,10 @@ const bringForward = function (onUpdateSvg) {
for (let i = items.length - 1; i >= 0; i--) {
items[i].insertAbove(nextSibling);
}
onUpdateSvg();
onUpdateImage();
};
const sendBackward = function (onUpdateSvg) {
const sendBackward = function (onUpdateImage) {
const items = getSelectedRootItems();
// Already at front
if (items.length === 0 || !items[0].previousSibling) {
@ -41,7 +41,7 @@ const sendBackward = function (onUpdateSvg) {
for (const item of items) {
item.insertBelow(previousSibling);
}
onUpdateSvg();
onUpdateImage();
};
const shouldShowSendBackward = function () {

View file

@ -28,25 +28,25 @@ const BoundingBoxModes = keyMirror({
* On mouse down, the type of function (move, scale, rotate) is determined based on what is clicked
* (scale handle, rotate handle, the object itself). This determines the mode of the tool, which then
* delegates actions to the MoveTool, RotateTool or ScaleTool accordingly.
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
* @param {!function} onUpdateImage A callback to call when the image visibly changes
*/
class BoundingBoxTool {
/**
* @param {Modes} mode Paint editor mode
* @param {function} setSelectedItems Callback to set the set of selected items in the Redux state
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
* @param {!function} onUpdateImage A callback to call when the image visibly changes
*/
constructor (mode, setSelectedItems, clearSelectedItems, onUpdateSvg) {
this.onUpdateSvg = onUpdateSvg;
constructor (mode, setSelectedItems, clearSelectedItems, onUpdateImage) {
this.onUpdateImage = onUpdateImage;
this.mode = null;
this.boundsPath = null;
this.boundsScaleHandles = [];
this.boundsRotHandles = [];
this._modeMap = {};
this._modeMap[BoundingBoxModes.SCALE] = new ScaleTool(onUpdateSvg);
this._modeMap[BoundingBoxModes.ROTATE] = new RotateTool(onUpdateSvg);
this._modeMap[BoundingBoxModes.MOVE] = new MoveTool(mode, setSelectedItems, clearSelectedItems, onUpdateSvg);
this._modeMap[BoundingBoxModes.SCALE] = new ScaleTool(onUpdateImage);
this._modeMap[BoundingBoxModes.ROTATE] = new RotateTool(onUpdateImage);
this._modeMap[BoundingBoxModes.MOVE] = new MoveTool(mode, setSelectedItems, clearSelectedItems, onUpdateImage);
}
/**

View file

@ -5,13 +5,13 @@ class HandleTool {
/**
* @param {function} setSelectedItems Callback to set the set of selected items in the Redux state
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
* @param {!function} onUpdateImage A callback to call when the image visibly changes
*/
constructor (setSelectedItems, clearSelectedItems, onUpdateSvg) {
constructor (setSelectedItems, clearSelectedItems, onUpdateImage) {
this.hitType = null;
this.setSelectedItems = setSelectedItems;
this.clearSelectedItems = clearSelectedItems;
this.onUpdateSvg = onUpdateSvg;
this.onUpdateImage = onUpdateImage;
this.selectedItems = [];
}
/**
@ -80,7 +80,7 @@ class HandleTool {
}
if (moved) {
this.setSelectedItems();
this.onUpdateSvg();
this.onUpdateImage();
}
this.selectedItems = [];
}

View file

@ -13,14 +13,14 @@ class MoveTool {
* @param {Modes} mode Paint editor mode
* @param {function} setSelectedItems Callback to set the set of selected items in the Redux state
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
* @param {!function} onUpdateImage A callback to call when the image visibly changes
*/
constructor (mode, setSelectedItems, clearSelectedItems, onUpdateSvg) {
constructor (mode, setSelectedItems, clearSelectedItems, onUpdateImage) {
this.mode = mode;
this.setSelectedItems = setSelectedItems;
this.clearSelectedItems = clearSelectedItems;
this.selectedItems = null;
this.onUpdateSvg = onUpdateSvg;
this.onUpdateImage = onUpdateImage;
this.boundsPath = null;
}
@ -56,7 +56,7 @@ class MoveTool {
}
this._select(item, true, hitProperties.subselect);
}
if (hitProperties.clone) cloneSelection(hitProperties.subselect, this.onUpdateSvg);
if (hitProperties.clone) cloneSelection(hitProperties.subselect, this.onUpdateImage);
this.selectedItems = this.mode === Modes.RESHAPE ? getSelectedLeafItems() : getSelectedRootItems();
if (this.boundsPath) {
this.selectedItems.push(this.boundsPath);
@ -116,7 +116,7 @@ class MoveTool {
this.selectedItems = null;
if (moved) {
this.onUpdateSvg();
this.onUpdateImage();
}
}
}

View file

@ -8,11 +8,11 @@ import paper from '@scratch/paper';
class NudgeTool {
/**
* @param {function} boundingBoxTool to control the bounding box
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
* @param {!function} onUpdateImage A callback to call when the image visibly changes
*/
constructor (boundingBoxTool, onUpdateSvg) {
constructor (boundingBoxTool, onUpdateImage) {
this.boundingBoxTool = boundingBoxTool;
this.onUpdateSvg = onUpdateSvg;
this.onUpdateImage = onUpdateImage;
}
onKeyDown (event) {
if (event.event.target instanceof HTMLInputElement) {
@ -47,7 +47,7 @@ class NudgeTool {
if (selected.length === 0) return;
if (event.key === 'up' || event.key === 'down' || event.key === 'left' || event.key === 'right') {
this.onUpdateSvg();
this.onUpdateImage();
}
}
}

View file

@ -8,9 +8,9 @@ class PointTool {
/**
* @param {function} setSelectedItems Callback to set the set of selected items in the Redux state
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
* @param {!function} onUpdateImage A callback to call when the image visibly changes
*/
constructor (setSelectedItems, clearSelectedItems, onUpdateSvg) {
constructor (setSelectedItems, clearSelectedItems, onUpdateImage) {
/**
* Deselection often does not happen until mouse up. If the mouse is dragged before
* mouse up, deselection is cancelled. This variable keeps track of which paper.Item to deselect.
@ -29,7 +29,7 @@ class PointTool {
this.selectedItems = null;
this.setSelectedItems = setSelectedItems;
this.clearSelectedItems = clearSelectedItems;
this.onUpdateSvg = onUpdateSvg;
this.onUpdateImage = onUpdateImage;
}
/**
@ -191,7 +191,7 @@ class PointTool {
this.selectedItems = null;
this.setSelectedItems();
if (moved) {
this.onUpdateSvg();
this.onUpdateImage();
}
}
}

View file

@ -43,22 +43,22 @@ class ReshapeTool extends paper.Tool {
* @param {function} clearHoveredItem Callback to clear the hovered item
* @param {function} setSelectedItems Callback to set the set of selected items in the Redux state
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
* @param {!function} onUpdateImage A callback to call when the image visibly changes
*/
constructor (setHoveredItem, clearHoveredItem, setSelectedItems, clearSelectedItems, onUpdateSvg) {
constructor (setHoveredItem, clearHoveredItem, setSelectedItems, clearSelectedItems, onUpdateImage) {
super();
this.setHoveredItem = setHoveredItem;
this.clearHoveredItem = clearHoveredItem;
this.onUpdateSvg = onUpdateSvg;
this.onUpdateImage = onUpdateImage;
this.prevHoveredItemId = null;
this.lastEvent = null;
this.active = false;
this.mode = ReshapeModes.SELECTION_BOX;
this._modeMap = {};
this._modeMap[ReshapeModes.FILL] =
new MoveTool(Modes.RESHAPE, setSelectedItems, clearSelectedItems, onUpdateSvg);
this._modeMap[ReshapeModes.POINT] = new PointTool(setSelectedItems, clearSelectedItems, onUpdateSvg);
this._modeMap[ReshapeModes.HANDLE] = new HandleTool(setSelectedItems, clearSelectedItems, onUpdateSvg);
new MoveTool(Modes.RESHAPE, setSelectedItems, clearSelectedItems, onUpdateImage);
this._modeMap[ReshapeModes.POINT] = new PointTool(setSelectedItems, clearSelectedItems, onUpdateImage);
this._modeMap[ReshapeModes.HANDLE] = new HandleTool(setSelectedItems, clearSelectedItems, onUpdateImage);
this._modeMap[ReshapeModes.SELECTION_BOX] =
new SelectionBoxTool(Modes.RESHAPE, setSelectedItems, clearSelectedItems);
@ -271,7 +271,7 @@ class ReshapeTool extends paper.Tool {
if (selected.length === 0) return;
if (event.key === 'up' || event.key === 'down' || event.key === 'left' || event.key === 'right') {
this.onUpdateSvg();
this.onUpdateImage();
}
}
deactivateTool () {
@ -279,7 +279,7 @@ class ReshapeTool extends paper.Tool {
this.clearHoveredItem();
this.setHoveredItem = null;
this.clearHoveredItem = null;
this.onUpdateSvg = null;
this.onUpdateImage = null;
this.lastEvent = null;
}
}

View file

@ -5,13 +5,13 @@ import paper from '@scratch/paper';
*/
class RotateTool {
/**
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
* @param {!function} onUpdateImage A callback to call when the image visibly changes
*/
constructor (onUpdateSvg) {
constructor (onUpdateImage) {
this.rotItems = [];
this.rotGroupPivot = null;
this.prevRot = 90;
this.onUpdateSvg = onUpdateSvg;
this.onUpdateImage = onUpdateImage;
}
/**
@ -54,7 +54,7 @@ class RotateTool {
this.rotGroupPivot = null;
this.prevRot = 90;
this.onUpdateSvg();
this.onUpdateImage();
}
}

View file

@ -7,9 +7,9 @@ import {getItems} from '../selection';
*/
class ScaleTool {
/**
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
* @param {!function} onUpdateImage A callback to call when the image visibly changes
*/
constructor (onUpdateSvg) {
constructor (onUpdateImage) {
this.active = false;
this.boundsPath = null;
this.pivot = null;
@ -20,7 +20,7 @@ class ScaleTool {
this.itemGroup = null;
// Lowest item above all scale items in z index
this.itemToInsertBelow = null;
this.onUpdateSvg = onUpdateSvg;
this.onUpdateImage = onUpdateImage;
}
/**
@ -144,7 +144,7 @@ class ScaleTool {
}
this.itemGroup.remove();
this.onUpdateSvg();
this.onUpdateImage();
this.active = false;
}
_getRectCornerNameByIndex (index) {

View file

@ -24,15 +24,15 @@ class SelectTool extends paper.Tool {
* @param {function} clearHoveredItem Callback to clear the hovered item
* @param {function} setSelectedItems Callback to set the set of selected items in the Redux state
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
* @param {!function} onUpdateImage A callback to call when the image visibly changes
*/
constructor (setHoveredItem, clearHoveredItem, setSelectedItems, clearSelectedItems, onUpdateSvg) {
constructor (setHoveredItem, clearHoveredItem, setSelectedItems, clearSelectedItems, onUpdateImage) {
super();
this.setHoveredItem = setHoveredItem;
this.clearHoveredItem = clearHoveredItem;
this.onUpdateSvg = onUpdateSvg;
this.boundingBoxTool = new BoundingBoxTool(Modes.SELECT, setSelectedItems, clearSelectedItems, onUpdateSvg);
const nudgeTool = new NudgeTool(this.boundingBoxTool, onUpdateSvg);
this.onUpdateImage = onUpdateImage;
this.boundingBoxTool = new BoundingBoxTool(Modes.SELECT, setSelectedItems, clearSelectedItems, onUpdateImage);
const nudgeTool = new NudgeTool(this.boundingBoxTool, onUpdateImage);
this.selectionBoxTool = new SelectionBoxTool(Modes.SELECT, setSelectedItems, clearSelectedItems);
this.selectionBoxMode = false;
this.prevHoveredItemId = null;
@ -140,7 +140,7 @@ class SelectTool extends paper.Tool {
this.boundingBoxTool.removeBoundsPath();
this.setHoveredItem = null;
this.clearHoveredItem = null;
this.onUpdateSvg = null;
this.onUpdateImage = null;
this.boundingBoxTool = null;
this.selectionBoxTool = null;
}

View file

@ -195,7 +195,7 @@ const getSelectedSegments = function () {
return segments;
};
const _deleteItemSelection = function (items, onUpdateSvg) {
const _deleteItemSelection = function (items, onUpdateImage) {
// @todo: Update toolbar state on change
if (items.length === 0) {
return false;
@ -203,12 +203,12 @@ const _deleteItemSelection = function (items, onUpdateSvg) {
for (let i = 0; i < items.length; i++) {
items[i].remove();
}
onUpdateSvg();
onUpdateImage();
return true;
};
// Return true if anything was removed
const _removeSelectedSegments = function (items, onUpdateSvg) {
const _removeSelectedSegments = function (items, onUpdateImage) {
const segmentsToRemove = [];
for (let i = 0; i < items.length; i++) {
@ -229,33 +229,33 @@ const _removeSelectedSegments = function (items, onUpdateSvg) {
removedSegments = true;
}
if (removedSegments) {
onUpdateSvg();
onUpdateImage();
}
return removedSegments;
};
// Return whether anything was deleted
const deleteSelection = function (mode, onUpdateSvg) {
const deleteSelection = function (mode, onUpdateImage) {
if (mode === Modes.RESHAPE) {
const selectedItems = getSelectedLeafItems();
// If there are points selected remove them. If not delete the item selected.
if (_removeSelectedSegments(selectedItems, onUpdateSvg)) {
if (_removeSelectedSegments(selectedItems, onUpdateImage)) {
return true;
}
return _deleteItemSelection(selectedItems, onUpdateSvg);
return _deleteItemSelection(selectedItems, onUpdateImage);
}
const selectedItems = getSelectedRootItems();
return _deleteItemSelection(selectedItems, onUpdateSvg);
return _deleteItemSelection(selectedItems, onUpdateImage);
};
const cloneSelection = function (recursive, onUpdateSvg) {
const cloneSelection = function (recursive, onUpdateImage) {
const selectedItems = recursive ? getSelectedLeafItems() : getSelectedRootItems();
for (let i = 0; i < selectedItems.length; i++) {
const item = selectedItems[i];
item.clone();
item.selected = false;
}
onUpdateSvg();
onUpdateImage();
};
const _checkBoundsItem = function (selectionRect, item, event) {

View file

@ -9,13 +9,13 @@ class FillTool extends paper.Tool {
/**
* @param {function} setHoveredItem Callback to set the hovered item
* @param {function} clearHoveredItem Callback to clear the hovered item
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
* @param {!function} onUpdateImage A callback to call when the image visibly changes
*/
constructor (setHoveredItem, clearHoveredItem, onUpdateSvg) {
constructor (setHoveredItem, clearHoveredItem, onUpdateImage) {
super();
this.setHoveredItem = setHoveredItem;
this.clearHoveredItem = clearHoveredItem;
this.onUpdateSvg = onUpdateSvg;
this.onUpdateImage = onUpdateImage;
// We have to set these functions instead of just declaring them because
// paper.js tools hook up the listeners in the setter functions.
@ -150,7 +150,7 @@ class FillTool extends paper.Tool {
this.fillItem = null;
this.addedFillItem = null;
this.fillItemOrigColor = null;
this.onUpdateSvg();
this.onUpdateImage();
}
}
_noStroke (item) {

View file

@ -15,15 +15,15 @@ class OvalTool extends paper.Tool {
/**
* @param {function} setSelectedItems Callback to set the set of selected items in the Redux state
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
* @param {!function} onUpdateImage A callback to call when the image visibly changes
*/
constructor (setSelectedItems, clearSelectedItems, onUpdateSvg) {
constructor (setSelectedItems, clearSelectedItems, onUpdateImage) {
super();
this.setSelectedItems = setSelectedItems;
this.clearSelectedItems = clearSelectedItems;
this.onUpdateSvg = onUpdateSvg;
this.boundingBoxTool = new BoundingBoxTool(Modes.OVAL, setSelectedItems, clearSelectedItems, onUpdateSvg);
const nudgeTool = new NudgeTool(this.boundingBoxTool, onUpdateSvg);
this.onUpdateImage = onUpdateImage;
this.boundingBoxTool = new BoundingBoxTool(Modes.OVAL, setSelectedItems, clearSelectedItems, onUpdateImage);
const nudgeTool = new NudgeTool(this.boundingBoxTool, onUpdateImage);
// We have to set these functions instead of just declaring them because
// paper.js tools hook up the listeners in the setter functions.
@ -120,7 +120,7 @@ class OvalTool extends paper.Tool {
ovalPath.selected = true;
this.setSelectedItems();
this.onUpdateSvg();
this.onUpdateImage();
}
}
this.active = false;

View file

@ -15,15 +15,15 @@ class RectTool extends paper.Tool {
/**
* @param {function} setSelectedItems Callback to set the set of selected items in the Redux state
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
* @param {!function} onUpdateImage A callback to call when the image visibly changes
*/
constructor (setSelectedItems, clearSelectedItems, onUpdateSvg) {
constructor (setSelectedItems, clearSelectedItems, onUpdateImage) {
super();
this.setSelectedItems = setSelectedItems;
this.clearSelectedItems = clearSelectedItems;
this.onUpdateSvg = onUpdateSvg;
this.boundingBoxTool = new BoundingBoxTool(Modes.RECT, setSelectedItems, clearSelectedItems, onUpdateSvg);
const nudgeTool = new NudgeTool(this.boundingBoxTool, onUpdateSvg);
this.onUpdateImage = onUpdateImage;
this.boundingBoxTool = new BoundingBoxTool(Modes.RECT, setSelectedItems, clearSelectedItems, onUpdateImage);
const nudgeTool = new NudgeTool(this.boundingBoxTool, onUpdateImage);
// We have to set these functions instead of just declaring them because
// paper.js tools hook up the listeners in the setter functions.
@ -113,7 +113,7 @@ class RectTool extends paper.Tool {
} else {
this.rect.selected = true;
this.setSelectedItems();
this.onUpdateSvg();
this.onUpdateImage();
this.rect = null;
}
}

View file

@ -10,15 +10,15 @@ class RoundedRectTool extends paper.Tool {
* @param {function} clearHoveredItem Callback to clear the hovered item
* @param {function} setSelectedItems Callback to set the set of selected items in the Redux state
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
* @param {!function} onUpdateImage A callback to call when the image visibly changes
*/
constructor (setHoveredItem, clearHoveredItem, setSelectedItems, clearSelectedItems, onUpdateSvg) {
constructor (setHoveredItem, clearHoveredItem, setSelectedItems, clearSelectedItems, onUpdateImage) {
super();
this.setHoveredItem = setHoveredItem;
this.clearHoveredItem = clearHoveredItem;
this.setSelectedItems = setSelectedItems;
this.clearSelectedItems = clearSelectedItems;
this.onUpdateSvg = onUpdateSvg;
this.onUpdateImage = onUpdateImage;
this.prevHoveredItemId = null;
// We have to set these functions instead of just declaring them because

View file

@ -34,18 +34,18 @@ class TextTool extends paper.Tool {
* @param {HTMLTextAreaElement} textAreaElement dom element for the editable text field
* @param {function} setSelectedItems Callback to set the set of selected items in the Redux state
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
* @param {!function} onUpdateImage A callback to call when the image visibly changes
* @param {!function} setTextEditTarget Call to set text editing target whenever text editing is active
*/
constructor (textAreaElement, setSelectedItems, clearSelectedItems, onUpdateSvg, setTextEditTarget) {
constructor (textAreaElement, setSelectedItems, clearSelectedItems, onUpdateImage, setTextEditTarget) {
super();
this.element = textAreaElement;
this.setSelectedItems = setSelectedItems;
this.clearSelectedItems = clearSelectedItems;
this.onUpdateSvg = onUpdateSvg;
this.onUpdateImage = onUpdateImage;
this.setTextEditTarget = setTextEditTarget;
this.boundingBoxTool = new BoundingBoxTool(Modes.TEXT, setSelectedItems, clearSelectedItems, onUpdateSvg);
this.nudgeTool = new NudgeTool(this.boundingBoxTool, onUpdateSvg);
this.boundingBoxTool = new BoundingBoxTool(Modes.TEXT, setSelectedItems, clearSelectedItems, onUpdateImage);
this.nudgeTool = new NudgeTool(this.boundingBoxTool, onUpdateImage);
this.lastEvent = null;
// We have to set these functions instead of just declaring them because
@ -249,7 +249,7 @@ class TextTool extends paper.Tool {
handleTextInput (event) {
// Save undo state if you paused typing for long enough.
if (this.lastTypeEvent && event.timeStamp - this.lastTypeEvent.timeStamp > TextTool.TYPING_TIMEOUT_MILLIS) {
this.onUpdateSvg();
this.onUpdateImage();
}
this.lastTypeEvent = event;
if (this.mode === TextTool.TEXT_EDIT_MODE) {
@ -317,7 +317,7 @@ class TextTool extends paper.Tool {
// If you finished editing a textbox, save undo state
if (this.textBox && this.textBox.content.trim().length) {
this.onUpdateSvg();
this.onUpdateImage();
}
}
deactivateTool () {

View file

@ -4,7 +4,6 @@ import paper from '@scratch/paper';
import {hideGuideLayers, showGuideLayers, getRaster} from '../helper/layer';
import Formats from '../lib/format';
import {isVector, isBitmap} from '../lib/format';
import log from '../log/log';
/**
* Take an undo snapshot
@ -20,7 +19,7 @@ const performSnapshot = function (dispatchPerformSnapshot, format) {
showGuideLayers(guideLayers);
};
const _restore = function (entry, setSelectedItems, onUpdateSvg) {
const _restore = function (entry, setSelectedItems, onUpdateImage) {
for (let i = paper.project.layers.length - 1; i >= 0; i--) {
const layer = paper.project.layers[i];
if (!layer.data.isBackgroundGuideLayer) {
@ -32,36 +31,30 @@ const _restore = function (entry, setSelectedItems, onUpdateSvg) {
setSelectedItems();
getRaster().onLoad = function () {
onUpdateSvg(true /* skipSnapshot */);
onUpdateImage(true /* skipSnapshot */);
};
if (getRaster().loaded) {
getRaster().onLoad();
}
};
const performUndo = function (undoState, dispatchPerformUndo, setSelectedItems, onUpdateSvg) {
const performUndo = function (undoState, dispatchPerformUndo, setSelectedItems, onUpdateImage) {
if (undoState.pointer > 0) {
const state = undoState.stack[undoState.pointer - 1];
_restore(state, setSelectedItems, onUpdateSvg);
_restore(state, setSelectedItems, onUpdateImage);
const format = isVector(state.paintEditorFormat) ? Formats.VECTOR_SKIP_CONVERT :
isBitmap(state.paintEditorFormat) ? Formats.BITMAP_SKIP_CONVERT : null;
if (!format) {
log.error(`Invalid format: ${state.paintEditorFormat}`);
}
dispatchPerformUndo(format);
}
};
const performRedo = function (undoState, dispatchPerformRedo, setSelectedItems, onUpdateSvg) {
const performRedo = function (undoState, dispatchPerformRedo, setSelectedItems, onUpdateImage) {
if (undoState.pointer >= 0 && undoState.pointer < undoState.stack.length - 1) {
const state = undoState.stack[undoState.pointer + 1];
_restore(state, setSelectedItems, onUpdateSvg);
_restore(state, setSelectedItems, onUpdateImage);
const format = isVector(state.paintEditorFormat) ? Formats.VECTOR_SKIP_CONVERT :
isBitmap(state.paintEditorFormat) ? Formats.BITMAP_SKIP_CONVERT : null;
if (!format) {
log.error(`Invalid format: ${state.paintEditorFormat}`);
}
dispatchPerformRedo(format);
}
};

View file

@ -28,30 +28,36 @@ class Playground extends React.Component {
super(props);
bindAll(this, [
'handleUpdateName',
'handleUpdateSvg'
'handleUpdateImage'
]);
this.state = {
name: 'meow',
rotationCenterX: 20,
rotationCenterY: 400,
svg: svgString
image: svgString
};
}
handleUpdateName (name) {
this.setState({name});
}
handleUpdateSvg (svg, rotationCenterX, rotationCenterY) {
console.log(svg);
handleUpdateImage (isVector, image, rotationCenterX, rotationCenterY) {
console.log(image);
console.log(`rotationCenterX: ${rotationCenterX} rotationCenterY: ${rotationCenterY}`);
this.setState({svg, rotationCenterX, rotationCenterY});
if (isVector) {
this.setState({image, rotationCenterX, rotationCenterY});
} else { // is Bitmap
const imageElement = new Image();
imageElement.src = image.toDataURL("image/png");
this.setState({imageElement, rotationCenterX, rotationCenterY});
}
}
render () {
return (
<PaintEditor
{...this.state}
svgId="meow"
imageId="meow"
onUpdateName={this.handleUpdateName}
onUpdateSvg={this.handleUpdateSvg}
onUpdateImage={this.handleUpdateImage}
/>
);
}

View file

@ -3,7 +3,7 @@ import log from '../log/log';
import {UNDO, REDO} from './undo';
const CHANGE_FORMAT = 'scratch-paint/formats/CHANGE_FORMAT';
const initialState = Formats.VECTOR;
const initialState = null;
const reducer = function (state, action) {
if (typeof state === 'undefined') state = initialState;
@ -13,6 +13,7 @@ const reducer = function (state, action) {
case REDO:
/* falls through */
case CHANGE_FORMAT:
if (!action.format) return state;
if (action.format in Formats) {
return action.format;
}

View file

@ -6,7 +6,7 @@ import {undo, redo} from '../../src/reducers/undo';
test('initialState', () => {
let defaultState;
expect(reducer(defaultState /* state */, {type: 'anything'} /* action */) in Formats).toBeTruthy();
expect(reducer(defaultState /* state */, {type: 'anything'} /* action */)).toBeNull();
});
test('changeFormat', () => {