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 ### 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. 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: In your parent component:
``` ```
import PaintEditor from 'scratch-paint'; import PaintEditor from 'scratch-paint';
... ...
<PaintEditor <PaintEditor
svg={optionalSvg} image={optionalImage}
rotationCenterX={optionalCenterPointXRelativeToSvgTopLeft} imageId={optionalId}
rotationCenterY={optionalCenterPointYRelativeToSvgTopLeft} rotationCenterX={optionalCenterPointXRelativeToTopLeft}
onUpdateSvg={handleUpdateSvgFunction} rotationCenterY={optionalCenterPointYRelativeToTopLeft}
onUpdateImage={handleUpdateImageFunction}
/> />
``` ```

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -9,11 +9,11 @@ import {ART_BOARD_WIDTH, ART_BOARD_HEIGHT} from '../view';
*/ */
class LineTool extends paper.Tool { 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(); super();
this.onUpdateSvg = onUpdateSvg; this.onUpdateImage = onUpdateImage;
// We have to set these functions instead of just declaring them because // We have to set these functions instead of just declaring them because
// paper.js tools hook up the listeners in the setter functions. // paper.js tools hook up the listeners in the setter functions.
@ -103,7 +103,7 @@ class LineTool extends paper.Tool {
this.drawTarget = getRaster(); this.drawTarget = getRaster();
forEachLinePoint(this.startPoint, event.point, this.draw.bind(this)); forEachLinePoint(this.startPoint, event.point, this.draw.bind(this));
this.drawTarget = null; this.drawTarget = null;
this.onUpdateSvg(); this.onUpdateImage();
this.lastPoint = null; this.lastPoint = null;
this.active = false; 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 * @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.broadBrushHelper = new BroadBrushHelper();
this.segmentBrushHelper = new SegmentBrushHelper(); this.segmentBrushHelper = new SegmentBrushHelper();
this.onUpdateSvg = onUpdateSvg; this.onUpdateImage = onUpdateImage;
this.clearSelectedItems = clearSelectedItems; this.clearSelectedItems = clearSelectedItems;
// The following are stored to check whether these have changed and the cursor preview needs to be redrawn. // 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 // Remove cursor preview during snapshot, then bring it back
blob.cursorPreview.remove(); blob.cursorPreview.remove();
blob.onUpdateSvg(); blob.onUpdateImage();
blob.cursorPreview.parent = getGuideLayer(); blob.cursorPreview.parent = getGuideLayer();
// Reset // Reset

View file

@ -11,10 +11,10 @@ const isGroup = function (item) {
* @param {!Array<paper.Item>} items Root level items to group * @param {!Array<paper.Item>} items Root level items to group
* @param {!function} clearSelectedItems Function to clear Redux state's selected items * @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} 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. * @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) { if (items.length > 0) {
const group = new paper.Group(items); const group = new paper.Group(items);
clearSelection(clearSelectedItems); clearSelection(clearSelectedItems);
@ -23,7 +23,7 @@ const groupItems = function (items, clearSelectedItems, setSelectedItems, onUpda
group.children[i].selected = true; group.children[i].selected = true;
} }
setSelectedItems(); setSelectedItems();
onUpdateSvg(); onUpdateImage();
return group; return group;
} }
return false; 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. * 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} 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} 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. * @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(); const items = getSelectedRootItems();
return groupItems(items, clearSelectedItems, setSelectedItems, onUpdateSvg); return groupItems(items, clearSelectedItems, setSelectedItems, onUpdateImage);
}; };
const _ungroupLoop = function (group, recursive, setSelectedItems) { 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. * 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 * 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. * SVG import, which shouldn't change the selection or undo state.
* *
* @param {!Array<paper.Item>} items Items to ungroup if they are groups * @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} 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) { if (items.length === 0) {
return; return;
} }
@ -102,8 +102,8 @@ const ungroupItems = function (items, setSelectedItems, onUpdateSvg) {
emptyGroups[j].remove(); emptyGroups[j].remove();
} }
// @todo: enable/disable grouping icons // @todo: enable/disable grouping icons
if (onUpdateSvg) { if (onUpdateImage) {
onUpdateSvg(); onUpdateImage();
} }
}; };
@ -112,12 +112,12 @@ const ungroupItems = function (items, setSelectedItems, onUpdateSvg) {
* *
* @param {!function} clearSelectedItems Function to clear Redux state's selected items * @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} 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(); const items = getSelectedRootItems();
clearSelection(clearSelectedItems); clearSelection(clearSelectedItems);
ungroupItems(items, setSelectedItems, onUpdateSvg); ungroupItems(items, setSelectedItems, onUpdateImage);
}; };
const getItemsGroup = function (item) { const getItemsGroup = function (item) {

View file

@ -1,22 +1,22 @@
import {getSelectedRootItems} from './selection'; import {getSelectedRootItems} from './selection';
const bringToFront = function (onUpdateSvg) { const bringToFront = function (onUpdateImage) {
const items = getSelectedRootItems(); const items = getSelectedRootItems();
for (const item of items) { for (const item of items) {
item.bringToFront(); item.bringToFront();
} }
onUpdateSvg(); onUpdateImage();
}; };
const sendToBack = function (onUpdateSvg) { const sendToBack = function (onUpdateImage) {
const items = getSelectedRootItems(); const items = getSelectedRootItems();
for (let i = items.length - 1; i >= 0; i--) { for (let i = items.length - 1; i >= 0; i--) {
items[i].sendToBack(); items[i].sendToBack();
} }
onUpdateSvg(); onUpdateImage();
}; };
const bringForward = function (onUpdateSvg) { const bringForward = function (onUpdateImage) {
const items = getSelectedRootItems(); const items = getSelectedRootItems();
// Already at front // Already at front
if (items.length === 0 || !items[items.length - 1].nextSibling) { 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--) { for (let i = items.length - 1; i >= 0; i--) {
items[i].insertAbove(nextSibling); items[i].insertAbove(nextSibling);
} }
onUpdateSvg(); onUpdateImage();
}; };
const sendBackward = function (onUpdateSvg) { const sendBackward = function (onUpdateImage) {
const items = getSelectedRootItems(); const items = getSelectedRootItems();
// Already at front // Already at front
if (items.length === 0 || !items[0].previousSibling) { if (items.length === 0 || !items[0].previousSibling) {
@ -41,7 +41,7 @@ const sendBackward = function (onUpdateSvg) {
for (const item of items) { for (const item of items) {
item.insertBelow(previousSibling); item.insertBelow(previousSibling);
} }
onUpdateSvg(); onUpdateImage();
}; };
const shouldShowSendBackward = function () { 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 * 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 * (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. * 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 { class BoundingBoxTool {
/** /**
* @param {Modes} mode Paint editor mode * @param {Modes} mode Paint editor mode
* @param {function} setSelectedItems Callback to set the set of selected items in the Redux state * @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} 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.onUpdateSvg = onUpdateSvg; this.onUpdateImage = onUpdateImage;
this.mode = null; this.mode = null;
this.boundsPath = null; this.boundsPath = null;
this.boundsScaleHandles = []; this.boundsScaleHandles = [];
this.boundsRotHandles = []; this.boundsRotHandles = [];
this._modeMap = {}; this._modeMap = {};
this._modeMap[BoundingBoxModes.SCALE] = new ScaleTool(onUpdateSvg); this._modeMap[BoundingBoxModes.SCALE] = new ScaleTool(onUpdateImage);
this._modeMap[BoundingBoxModes.ROTATE] = new RotateTool(onUpdateSvg); this._modeMap[BoundingBoxModes.ROTATE] = new RotateTool(onUpdateImage);
this._modeMap[BoundingBoxModes.MOVE] = new MoveTool(mode, setSelectedItems, clearSelectedItems, onUpdateSvg); 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} 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} 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.hitType = null;
this.setSelectedItems = setSelectedItems; this.setSelectedItems = setSelectedItems;
this.clearSelectedItems = clearSelectedItems; this.clearSelectedItems = clearSelectedItems;
this.onUpdateSvg = onUpdateSvg; this.onUpdateImage = onUpdateImage;
this.selectedItems = []; this.selectedItems = [];
} }
/** /**
@ -80,7 +80,7 @@ class HandleTool {
} }
if (moved) { if (moved) {
this.setSelectedItems(); this.setSelectedItems();
this.onUpdateSvg(); this.onUpdateImage();
} }
this.selectedItems = []; this.selectedItems = [];
} }

View file

@ -13,14 +13,14 @@ class MoveTool {
* @param {Modes} mode Paint editor mode * @param {Modes} mode Paint editor mode
* @param {function} setSelectedItems Callback to set the set of selected items in the Redux state * @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} 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.mode = mode;
this.setSelectedItems = setSelectedItems; this.setSelectedItems = setSelectedItems;
this.clearSelectedItems = clearSelectedItems; this.clearSelectedItems = clearSelectedItems;
this.selectedItems = null; this.selectedItems = null;
this.onUpdateSvg = onUpdateSvg; this.onUpdateImage = onUpdateImage;
this.boundsPath = null; this.boundsPath = null;
} }
@ -56,7 +56,7 @@ class MoveTool {
} }
this._select(item, true, hitProperties.subselect); 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(); this.selectedItems = this.mode === Modes.RESHAPE ? getSelectedLeafItems() : getSelectedRootItems();
if (this.boundsPath) { if (this.boundsPath) {
this.selectedItems.push(this.boundsPath); this.selectedItems.push(this.boundsPath);
@ -116,7 +116,7 @@ class MoveTool {
this.selectedItems = null; this.selectedItems = null;
if (moved) { if (moved) {
this.onUpdateSvg(); this.onUpdateImage();
} }
} }
} }

View file

@ -8,11 +8,11 @@ import paper from '@scratch/paper';
class NudgeTool { class NudgeTool {
/** /**
* @param {function} boundingBoxTool to control the bounding box * @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.boundingBoxTool = boundingBoxTool;
this.onUpdateSvg = onUpdateSvg; this.onUpdateImage = onUpdateImage;
} }
onKeyDown (event) { onKeyDown (event) {
if (event.event.target instanceof HTMLInputElement) { if (event.event.target instanceof HTMLInputElement) {
@ -47,7 +47,7 @@ class NudgeTool {
if (selected.length === 0) return; if (selected.length === 0) return;
if (event.key === 'up' || event.key === 'down' || event.key === 'left' || event.key === 'right') { 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} 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} 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 * 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. * 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.selectedItems = null;
this.setSelectedItems = setSelectedItems; this.setSelectedItems = setSelectedItems;
this.clearSelectedItems = clearSelectedItems; this.clearSelectedItems = clearSelectedItems;
this.onUpdateSvg = onUpdateSvg; this.onUpdateImage = onUpdateImage;
} }
/** /**
@ -191,7 +191,7 @@ class PointTool {
this.selectedItems = null; this.selectedItems = null;
this.setSelectedItems(); this.setSelectedItems();
if (moved) { 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} clearHoveredItem Callback to clear the hovered item
* @param {function} setSelectedItems Callback to set the set of selected items in the Redux state * @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} 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(); super();
this.setHoveredItem = setHoveredItem; this.setHoveredItem = setHoveredItem;
this.clearHoveredItem = clearHoveredItem; this.clearHoveredItem = clearHoveredItem;
this.onUpdateSvg = onUpdateSvg; this.onUpdateImage = onUpdateImage;
this.prevHoveredItemId = null; this.prevHoveredItemId = null;
this.lastEvent = null; this.lastEvent = null;
this.active = false; this.active = false;
this.mode = ReshapeModes.SELECTION_BOX; this.mode = ReshapeModes.SELECTION_BOX;
this._modeMap = {}; this._modeMap = {};
this._modeMap[ReshapeModes.FILL] = this._modeMap[ReshapeModes.FILL] =
new MoveTool(Modes.RESHAPE, setSelectedItems, clearSelectedItems, onUpdateSvg); new MoveTool(Modes.RESHAPE, setSelectedItems, clearSelectedItems, onUpdateImage);
this._modeMap[ReshapeModes.POINT] = new PointTool(setSelectedItems, clearSelectedItems, onUpdateSvg); this._modeMap[ReshapeModes.POINT] = new PointTool(setSelectedItems, clearSelectedItems, onUpdateImage);
this._modeMap[ReshapeModes.HANDLE] = new HandleTool(setSelectedItems, clearSelectedItems, onUpdateSvg); this._modeMap[ReshapeModes.HANDLE] = new HandleTool(setSelectedItems, clearSelectedItems, onUpdateImage);
this._modeMap[ReshapeModes.SELECTION_BOX] = this._modeMap[ReshapeModes.SELECTION_BOX] =
new SelectionBoxTool(Modes.RESHAPE, setSelectedItems, clearSelectedItems); new SelectionBoxTool(Modes.RESHAPE, setSelectedItems, clearSelectedItems);
@ -271,7 +271,7 @@ class ReshapeTool extends paper.Tool {
if (selected.length === 0) return; if (selected.length === 0) return;
if (event.key === 'up' || event.key === 'down' || event.key === 'left' || event.key === 'right') { if (event.key === 'up' || event.key === 'down' || event.key === 'left' || event.key === 'right') {
this.onUpdateSvg(); this.onUpdateImage();
} }
} }
deactivateTool () { deactivateTool () {
@ -279,7 +279,7 @@ class ReshapeTool extends paper.Tool {
this.clearHoveredItem(); this.clearHoveredItem();
this.setHoveredItem = null; this.setHoveredItem = null;
this.clearHoveredItem = null; this.clearHoveredItem = null;
this.onUpdateSvg = null; this.onUpdateImage = null;
this.lastEvent = null; this.lastEvent = null;
} }
} }

View file

@ -5,13 +5,13 @@ import paper from '@scratch/paper';
*/ */
class RotateTool { 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.rotItems = [];
this.rotGroupPivot = null; this.rotGroupPivot = null;
this.prevRot = 90; this.prevRot = 90;
this.onUpdateSvg = onUpdateSvg; this.onUpdateImage = onUpdateImage;
} }
/** /**
@ -54,7 +54,7 @@ class RotateTool {
this.rotGroupPivot = null; this.rotGroupPivot = null;
this.prevRot = 90; this.prevRot = 90;
this.onUpdateSvg(); this.onUpdateImage();
} }
} }

View file

@ -7,9 +7,9 @@ import {getItems} from '../selection';
*/ */
class ScaleTool { 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.active = false;
this.boundsPath = null; this.boundsPath = null;
this.pivot = null; this.pivot = null;
@ -20,7 +20,7 @@ class ScaleTool {
this.itemGroup = null; this.itemGroup = null;
// Lowest item above all scale items in z index // Lowest item above all scale items in z index
this.itemToInsertBelow = null; this.itemToInsertBelow = null;
this.onUpdateSvg = onUpdateSvg; this.onUpdateImage = onUpdateImage;
} }
/** /**
@ -144,7 +144,7 @@ class ScaleTool {
} }
this.itemGroup.remove(); this.itemGroup.remove();
this.onUpdateSvg(); this.onUpdateImage();
this.active = false; this.active = false;
} }
_getRectCornerNameByIndex (index) { _getRectCornerNameByIndex (index) {

View file

@ -24,15 +24,15 @@ class SelectTool extends paper.Tool {
* @param {function} clearHoveredItem Callback to clear the hovered item * @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} 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} 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(); super();
this.setHoveredItem = setHoveredItem; this.setHoveredItem = setHoveredItem;
this.clearHoveredItem = clearHoveredItem; this.clearHoveredItem = clearHoveredItem;
this.onUpdateSvg = onUpdateSvg; this.onUpdateImage = onUpdateImage;
this.boundingBoxTool = new BoundingBoxTool(Modes.SELECT, setSelectedItems, clearSelectedItems, onUpdateSvg); this.boundingBoxTool = new BoundingBoxTool(Modes.SELECT, setSelectedItems, clearSelectedItems, onUpdateImage);
const nudgeTool = new NudgeTool(this.boundingBoxTool, onUpdateSvg); const nudgeTool = new NudgeTool(this.boundingBoxTool, onUpdateImage);
this.selectionBoxTool = new SelectionBoxTool(Modes.SELECT, setSelectedItems, clearSelectedItems); this.selectionBoxTool = new SelectionBoxTool(Modes.SELECT, setSelectedItems, clearSelectedItems);
this.selectionBoxMode = false; this.selectionBoxMode = false;
this.prevHoveredItemId = null; this.prevHoveredItemId = null;
@ -140,7 +140,7 @@ class SelectTool extends paper.Tool {
this.boundingBoxTool.removeBoundsPath(); this.boundingBoxTool.removeBoundsPath();
this.setHoveredItem = null; this.setHoveredItem = null;
this.clearHoveredItem = null; this.clearHoveredItem = null;
this.onUpdateSvg = null; this.onUpdateImage = null;
this.boundingBoxTool = null; this.boundingBoxTool = null;
this.selectionBoxTool = null; this.selectionBoxTool = null;
} }

View file

@ -195,7 +195,7 @@ const getSelectedSegments = function () {
return segments; return segments;
}; };
const _deleteItemSelection = function (items, onUpdateSvg) { const _deleteItemSelection = function (items, onUpdateImage) {
// @todo: Update toolbar state on change // @todo: Update toolbar state on change
if (items.length === 0) { if (items.length === 0) {
return false; return false;
@ -203,12 +203,12 @@ const _deleteItemSelection = function (items, onUpdateSvg) {
for (let i = 0; i < items.length; i++) { for (let i = 0; i < items.length; i++) {
items[i].remove(); items[i].remove();
} }
onUpdateSvg(); onUpdateImage();
return true; return true;
}; };
// Return true if anything was removed // Return true if anything was removed
const _removeSelectedSegments = function (items, onUpdateSvg) { const _removeSelectedSegments = function (items, onUpdateImage) {
const segmentsToRemove = []; const segmentsToRemove = [];
for (let i = 0; i < items.length; i++) { for (let i = 0; i < items.length; i++) {
@ -229,33 +229,33 @@ const _removeSelectedSegments = function (items, onUpdateSvg) {
removedSegments = true; removedSegments = true;
} }
if (removedSegments) { if (removedSegments) {
onUpdateSvg(); onUpdateImage();
} }
return removedSegments; return removedSegments;
}; };
// Return whether anything was deleted // Return whether anything was deleted
const deleteSelection = function (mode, onUpdateSvg) { const deleteSelection = function (mode, onUpdateImage) {
if (mode === Modes.RESHAPE) { if (mode === Modes.RESHAPE) {
const selectedItems = getSelectedLeafItems(); const selectedItems = getSelectedLeafItems();
// If there are points selected remove them. If not delete the item selected. // If there are points selected remove them. If not delete the item selected.
if (_removeSelectedSegments(selectedItems, onUpdateSvg)) { if (_removeSelectedSegments(selectedItems, onUpdateImage)) {
return true; return true;
} }
return _deleteItemSelection(selectedItems, onUpdateSvg); return _deleteItemSelection(selectedItems, onUpdateImage);
} }
const selectedItems = getSelectedRootItems(); 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(); const selectedItems = recursive ? getSelectedLeafItems() : getSelectedRootItems();
for (let i = 0; i < selectedItems.length; i++) { for (let i = 0; i < selectedItems.length; i++) {
const item = selectedItems[i]; const item = selectedItems[i];
item.clone(); item.clone();
item.selected = false; item.selected = false;
} }
onUpdateSvg(); onUpdateImage();
}; };
const _checkBoundsItem = function (selectionRect, item, event) { 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} setHoveredItem Callback to set the hovered item
* @param {function} clearHoveredItem Callback to clear 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(); super();
this.setHoveredItem = setHoveredItem; this.setHoveredItem = setHoveredItem;
this.clearHoveredItem = clearHoveredItem; this.clearHoveredItem = clearHoveredItem;
this.onUpdateSvg = onUpdateSvg; this.onUpdateImage = onUpdateImage;
// We have to set these functions instead of just declaring them because // We have to set these functions instead of just declaring them because
// paper.js tools hook up the listeners in the setter functions. // paper.js tools hook up the listeners in the setter functions.
@ -150,7 +150,7 @@ class FillTool extends paper.Tool {
this.fillItem = null; this.fillItem = null;
this.addedFillItem = null; this.addedFillItem = null;
this.fillItemOrigColor = null; this.fillItemOrigColor = null;
this.onUpdateSvg(); this.onUpdateImage();
} }
} }
_noStroke (item) { _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} 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} 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(); super();
this.setSelectedItems = setSelectedItems; this.setSelectedItems = setSelectedItems;
this.clearSelectedItems = clearSelectedItems; this.clearSelectedItems = clearSelectedItems;
this.onUpdateSvg = onUpdateSvg; this.onUpdateImage = onUpdateImage;
this.boundingBoxTool = new BoundingBoxTool(Modes.OVAL, setSelectedItems, clearSelectedItems, onUpdateSvg); this.boundingBoxTool = new BoundingBoxTool(Modes.OVAL, setSelectedItems, clearSelectedItems, onUpdateImage);
const nudgeTool = new NudgeTool(this.boundingBoxTool, onUpdateSvg); const nudgeTool = new NudgeTool(this.boundingBoxTool, onUpdateImage);
// We have to set these functions instead of just declaring them because // We have to set these functions instead of just declaring them because
// paper.js tools hook up the listeners in the setter functions. // paper.js tools hook up the listeners in the setter functions.
@ -120,7 +120,7 @@ class OvalTool extends paper.Tool {
ovalPath.selected = true; ovalPath.selected = true;
this.setSelectedItems(); this.setSelectedItems();
this.onUpdateSvg(); this.onUpdateImage();
} }
} }
this.active = false; 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} 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} 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(); super();
this.setSelectedItems = setSelectedItems; this.setSelectedItems = setSelectedItems;
this.clearSelectedItems = clearSelectedItems; this.clearSelectedItems = clearSelectedItems;
this.onUpdateSvg = onUpdateSvg; this.onUpdateImage = onUpdateImage;
this.boundingBoxTool = new BoundingBoxTool(Modes.RECT, setSelectedItems, clearSelectedItems, onUpdateSvg); this.boundingBoxTool = new BoundingBoxTool(Modes.RECT, setSelectedItems, clearSelectedItems, onUpdateImage);
const nudgeTool = new NudgeTool(this.boundingBoxTool, onUpdateSvg); const nudgeTool = new NudgeTool(this.boundingBoxTool, onUpdateImage);
// We have to set these functions instead of just declaring them because // We have to set these functions instead of just declaring them because
// paper.js tools hook up the listeners in the setter functions. // paper.js tools hook up the listeners in the setter functions.
@ -113,7 +113,7 @@ class RectTool extends paper.Tool {
} else { } else {
this.rect.selected = true; this.rect.selected = true;
this.setSelectedItems(); this.setSelectedItems();
this.onUpdateSvg(); this.onUpdateImage();
this.rect = null; 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} clearHoveredItem Callback to clear the hovered item
* @param {function} setSelectedItems Callback to set the set of selected items in the Redux state * @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} 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(); super();
this.setHoveredItem = setHoveredItem; this.setHoveredItem = setHoveredItem;
this.clearHoveredItem = clearHoveredItem; this.clearHoveredItem = clearHoveredItem;
this.setSelectedItems = setSelectedItems; this.setSelectedItems = setSelectedItems;
this.clearSelectedItems = clearSelectedItems; this.clearSelectedItems = clearSelectedItems;
this.onUpdateSvg = onUpdateSvg; this.onUpdateImage = onUpdateImage;
this.prevHoveredItemId = null; this.prevHoveredItemId = null;
// We have to set these functions instead of just declaring them because // 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 {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} 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} 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 * @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(); super();
this.element = textAreaElement; this.element = textAreaElement;
this.setSelectedItems = setSelectedItems; this.setSelectedItems = setSelectedItems;
this.clearSelectedItems = clearSelectedItems; this.clearSelectedItems = clearSelectedItems;
this.onUpdateSvg = onUpdateSvg; this.onUpdateImage = onUpdateImage;
this.setTextEditTarget = setTextEditTarget; this.setTextEditTarget = setTextEditTarget;
this.boundingBoxTool = new BoundingBoxTool(Modes.TEXT, setSelectedItems, clearSelectedItems, onUpdateSvg); this.boundingBoxTool = new BoundingBoxTool(Modes.TEXT, setSelectedItems, clearSelectedItems, onUpdateImage);
this.nudgeTool = new NudgeTool(this.boundingBoxTool, onUpdateSvg); this.nudgeTool = new NudgeTool(this.boundingBoxTool, onUpdateImage);
this.lastEvent = null; this.lastEvent = null;
// We have to set these functions instead of just declaring them because // We have to set these functions instead of just declaring them because
@ -249,7 +249,7 @@ class TextTool extends paper.Tool {
handleTextInput (event) { handleTextInput (event) {
// Save undo state if you paused typing for long enough. // Save undo state if you paused typing for long enough.
if (this.lastTypeEvent && event.timeStamp - this.lastTypeEvent.timeStamp > TextTool.TYPING_TIMEOUT_MILLIS) { if (this.lastTypeEvent && event.timeStamp - this.lastTypeEvent.timeStamp > TextTool.TYPING_TIMEOUT_MILLIS) {
this.onUpdateSvg(); this.onUpdateImage();
} }
this.lastTypeEvent = event; this.lastTypeEvent = event;
if (this.mode === TextTool.TEXT_EDIT_MODE) { 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 you finished editing a textbox, save undo state
if (this.textBox && this.textBox.content.trim().length) { if (this.textBox && this.textBox.content.trim().length) {
this.onUpdateSvg(); this.onUpdateImage();
} }
} }
deactivateTool () { deactivateTool () {

View file

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

View file

@ -28,30 +28,36 @@ class Playground extends React.Component {
super(props); super(props);
bindAll(this, [ bindAll(this, [
'handleUpdateName', 'handleUpdateName',
'handleUpdateSvg' 'handleUpdateImage'
]); ]);
this.state = { this.state = {
name: 'meow', name: 'meow',
rotationCenterX: 20, rotationCenterX: 20,
rotationCenterY: 400, rotationCenterY: 400,
svg: svgString image: svgString
}; };
} }
handleUpdateName (name) { handleUpdateName (name) {
this.setState({name}); this.setState({name});
} }
handleUpdateSvg (svg, rotationCenterX, rotationCenterY) { handleUpdateImage (isVector, image, rotationCenterX, rotationCenterY) {
console.log(svg); console.log(image);
console.log(`rotationCenterX: ${rotationCenterX} rotationCenterY: ${rotationCenterY}`); 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 () { render () {
return ( return (
<PaintEditor <PaintEditor
{...this.state} {...this.state}
svgId="meow" imageId="meow"
onUpdateName={this.handleUpdateName} 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'; import {UNDO, REDO} from './undo';
const CHANGE_FORMAT = 'scratch-paint/formats/CHANGE_FORMAT'; const CHANGE_FORMAT = 'scratch-paint/formats/CHANGE_FORMAT';
const initialState = Formats.VECTOR; const initialState = null;
const reducer = function (state, action) { const reducer = function (state, action) {
if (typeof state === 'undefined') state = initialState; if (typeof state === 'undefined') state = initialState;
@ -13,6 +13,7 @@ const reducer = function (state, action) {
case REDO: case REDO:
/* falls through */ /* falls through */
case CHANGE_FORMAT: case CHANGE_FORMAT:
if (!action.format) return state;
if (action.format in Formats) { if (action.format in Formats) {
return action.format; return action.format;
} }

View file

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