mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2024-12-22 13:32:28 -05:00
update button state when undoing between modes, hide vector tools
This commit is contained in:
parent
6e4ab3191a
commit
a6e7fb4251
9 changed files with 100 additions and 29 deletions
|
@ -150,6 +150,10 @@ $border-radius: 0.25rem;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.zoom-controls {
|
.zoom-controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row-reverse;
|
flex-direction: row-reverse;
|
||||||
|
|
|
@ -35,6 +35,7 @@ import StrokeWidthIndicatorComponent from '../../containers/stroke-width-indicat
|
||||||
import TextMode from '../../containers/text-mode.jsx';
|
import TextMode from '../../containers/text-mode.jsx';
|
||||||
|
|
||||||
import Formats from '../../lib/format';
|
import Formats from '../../lib/format';
|
||||||
|
import {isVector} from '../../lib/format';
|
||||||
import layout from '../../lib/layout-constants';
|
import layout from '../../lib/layout-constants';
|
||||||
import styles from './paint-editor.css';
|
import styles from './paint-editor.css';
|
||||||
|
|
||||||
|
@ -342,7 +343,7 @@ const PaintEditorComponent = props => {
|
||||||
<div className={styles.topAlignRow}>
|
<div className={styles.topAlignRow}>
|
||||||
{/* Modes */}
|
{/* Modes */}
|
||||||
{props.canvas !== null ? ( // eslint-disable-line no-negated-condition
|
{props.canvas !== null ? ( // eslint-disable-line no-negated-condition
|
||||||
<div className={styles.modeSelector}>
|
<div className={isVector(props.format) ? styles.modeSelector : styles.hidden}>
|
||||||
<SelectMode
|
<SelectMode
|
||||||
onUpdateSvg={props.onUpdateSvg}
|
onUpdateSvg={props.onUpdateSvg}
|
||||||
/>
|
/>
|
||||||
|
@ -408,7 +409,7 @@ const PaintEditorComponent = props => {
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.canvasControls}>
|
<div className={styles.canvasControls}>
|
||||||
{props.format === Formats.VECTOR ?
|
{isVector(props.format) ?
|
||||||
<Button
|
<Button
|
||||||
className={styles.bitmapButton}
|
className={styles.bitmapButton}
|
||||||
onClick={props.onSwitchToBitmap}
|
onClick={props.onSwitchToBitmap}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import EyeDropperTool from '../helper/tools/eye-dropper';
|
||||||
|
|
||||||
import Modes from '../lib/modes';
|
import Modes from '../lib/modes';
|
||||||
import Formats from '../lib/format';
|
import Formats from '../lib/format';
|
||||||
|
import {isBitmap} from '../lib/format';
|
||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
import bindAll from 'lodash.bindall';
|
import bindAll from 'lodash.bindall';
|
||||||
|
|
||||||
|
@ -93,7 +94,7 @@ class PaintEditor extends React.Component {
|
||||||
resetZoom();
|
resetZoom();
|
||||||
|
|
||||||
let raster;
|
let raster;
|
||||||
if (this.props.format === Formats.BITMAP) {
|
if (isBitmap(this.props.format)) {
|
||||||
// @todo export bitmap here
|
// @todo export bitmap here
|
||||||
raster = trim(getRaster());
|
raster = trim(getRaster());
|
||||||
if (raster.width === 0 || raster.height === 0) {
|
if (raster.width === 0 || raster.height === 0) {
|
||||||
|
@ -116,13 +117,12 @@ class PaintEditor extends React.Component {
|
||||||
paper.project.view.center.y - bounds.y);
|
paper.project.view.center.y - bounds.y);
|
||||||
|
|
||||||
showGuideLayers(guideLayers);
|
showGuideLayers(guideLayers);
|
||||||
|
if (raster) raster.remove();
|
||||||
|
|
||||||
if (!skipSnapshot) {
|
if (!skipSnapshot) {
|
||||||
performSnapshot(this.props.undoSnapshot);
|
performSnapshot(this.props.undoSnapshot, this.props.format);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (raster) raster.remove();
|
|
||||||
|
|
||||||
// Restore old zoom
|
// Restore old zoom
|
||||||
paper.project.view.zoom = oldZoom;
|
paper.project.view.zoom = oldZoom;
|
||||||
paper.project.view.center = oldCenter;
|
paper.project.view.center = oldCenter;
|
||||||
|
@ -363,11 +363,11 @@ const mapDispatchToProps = dispatch => ({
|
||||||
// set redux values to default for eye dropper reducer
|
// set redux values to default for eye dropper reducer
|
||||||
dispatch(deactivateEyeDropper());
|
dispatch(deactivateEyeDropper());
|
||||||
},
|
},
|
||||||
onUndo: () => {
|
onUndo: format => {
|
||||||
dispatch(undo());
|
dispatch(undo(format));
|
||||||
},
|
},
|
||||||
onRedo: () => {
|
onRedo: format => {
|
||||||
dispatch(redo());
|
dispatch(redo(format));
|
||||||
},
|
},
|
||||||
undoSnapshot: snapshot => {
|
undoSnapshot: snapshot => {
|
||||||
dispatch(undoSnapshot(snapshot));
|
dispatch(undoSnapshot(snapshot));
|
||||||
|
|
|
@ -20,6 +20,8 @@ import {clearHoveredItem} from '../reducers/hover';
|
||||||
import {clearPasteOffset} from '../reducers/clipboard';
|
import {clearPasteOffset} from '../reducers/clipboard';
|
||||||
import {updateViewBounds} from '../reducers/view-bounds';
|
import {updateViewBounds} from '../reducers/view-bounds';
|
||||||
|
|
||||||
|
import {isVector, isBitmap} from '../lib/format';
|
||||||
|
|
||||||
import styles from './paper-canvas.css';
|
import styles from './paper-canvas.css';
|
||||||
|
|
||||||
class PaperCanvas extends React.Component {
|
class PaperCanvas extends React.Component {
|
||||||
|
@ -50,15 +52,15 @@ class PaperCanvas extends React.Component {
|
||||||
if (this.props.svg) {
|
if (this.props.svg) {
|
||||||
this.importSvg(this.props.svg, this.props.rotationCenterX, this.props.rotationCenterY);
|
this.importSvg(this.props.svg, this.props.rotationCenterX, this.props.rotationCenterY);
|
||||||
} else {
|
} else {
|
||||||
performSnapshot(this.props.undoSnapshot);
|
performSnapshot(this.props.undoSnapshot, this.props.format);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
componentWillReceiveProps (newProps) {
|
componentWillReceiveProps (newProps) {
|
||||||
if (this.props.svgId !== newProps.svgId) {
|
if (this.props.svgId !== newProps.svgId) {
|
||||||
this.switchCostume(newProps.svg, newProps.rotationCenterX, newProps.rotationCenterY);
|
this.switchCostume(newProps.svg, newProps.rotationCenterX, newProps.rotationCenterY);
|
||||||
} else if (this.props.format === Formats.VECTOR && newProps.format === Formats.BITMAP) {
|
} else if (isVector(this.props.format) && newProps.format === Formats.BITMAP) {
|
||||||
this.convertToBitmap();
|
this.convertToBitmap();
|
||||||
} else if (this.props.format === Formats.BITMAP && newProps.format === Formats.VECTOR) {
|
} else if (isBitmap(this.props.format) && newProps.format === Formats.VECTOR) {
|
||||||
this.convertToVector();
|
this.convertToVector();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,7 +123,7 @@ class PaperCanvas extends React.Component {
|
||||||
paper.project.view.zoom = oldZoom;
|
paper.project.view.zoom = oldZoom;
|
||||||
paper.project.view.center = oldCenter;
|
paper.project.view.center = oldCenter;
|
||||||
} else {
|
} else {
|
||||||
performSnapshot(this.props.undoSnapshot);
|
performSnapshot(this.props.undoSnapshot, this.props.format);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
importSvg (svg, rotationCenterX, rotationCenterY) {
|
importSvg (svg, rotationCenterX, rotationCenterY) {
|
||||||
|
@ -156,7 +158,7 @@ class PaperCanvas extends React.Component {
|
||||||
if (!item) {
|
if (!item) {
|
||||||
log.error('SVG import failed:');
|
log.error('SVG import failed:');
|
||||||
log.info(svg);
|
log.info(svg);
|
||||||
performSnapshot(paperCanvas.props.undoSnapshot);
|
performSnapshot(paperCanvas.props.undoSnapshot, paperCanvas.props.format);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const itemWidth = item.bounds.width;
|
const itemWidth = item.bounds.width;
|
||||||
|
@ -198,7 +200,7 @@ class PaperCanvas extends React.Component {
|
||||||
ungroupItems([item]);
|
ungroupItems([item]);
|
||||||
}
|
}
|
||||||
|
|
||||||
performSnapshot(paperCanvas.props.undoSnapshot);
|
performSnapshot(paperCanvas.props.undoSnapshot, paperCanvas.props.format);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,20 @@
|
||||||
// modifed from https://github.com/memononen/stylii
|
// modifed from https://github.com/memononen/stylii
|
||||||
import paper from '@scratch/paper';
|
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 {isVector, isBitmap} from '../lib/format';
|
||||||
|
import log from '../log/log';
|
||||||
|
|
||||||
const performSnapshot = function (dispatchPerformSnapshot) {
|
/**
|
||||||
|
* Take an undo snapshot
|
||||||
|
* @param {function} dispatchPerformSnapshot Callback to dispatch a state update
|
||||||
|
* @param {Formats} either bitmap or vector
|
||||||
|
*/
|
||||||
|
const performSnapshot = function (dispatchPerformSnapshot, format) {
|
||||||
const guideLayers = hideGuideLayers();
|
const guideLayers = hideGuideLayers();
|
||||||
dispatchPerformSnapshot({
|
dispatchPerformSnapshot({
|
||||||
json: paper.project.exportJSON({asString: false})
|
json: paper.project.exportJSON({asString: false}),
|
||||||
|
paintEditorFormat: format
|
||||||
});
|
});
|
||||||
showGuideLayers(guideLayers);
|
showGuideLayers(guideLayers);
|
||||||
};
|
};
|
||||||
|
@ -32,16 +41,28 @@ const _restore = function (entry, setSelectedItems, onUpdateSvg) {
|
||||||
|
|
||||||
const performUndo = function (undoState, dispatchPerformUndo, setSelectedItems, onUpdateSvg) {
|
const performUndo = function (undoState, dispatchPerformUndo, setSelectedItems, onUpdateSvg) {
|
||||||
if (undoState.pointer > 0) {
|
if (undoState.pointer > 0) {
|
||||||
_restore(undoState.stack[undoState.pointer - 1], setSelectedItems, onUpdateSvg);
|
const state = undoState.stack[undoState.pointer - 1];
|
||||||
dispatchPerformUndo();
|
_restore(state, setSelectedItems, onUpdateSvg);
|
||||||
|
const format = isVector(state.paintEditorFormat) ? Formats.UNDO_VECTOR :
|
||||||
|
isBitmap(state.paintEditorFormat) ? Formats.UNDO_BITMAP : null;
|
||||||
|
if (!format) {
|
||||||
|
log.error(`Invalid format: ${state.paintEditorFormat}`);
|
||||||
|
}
|
||||||
|
dispatchPerformUndo(format);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const performRedo = function (undoState, dispatchPerformRedo, setSelectedItems, onUpdateSvg) {
|
const performRedo = function (undoState, dispatchPerformRedo, setSelectedItems, onUpdateSvg) {
|
||||||
if (undoState.pointer >= 0 && undoState.pointer < undoState.stack.length - 1) {
|
if (undoState.pointer >= 0 && undoState.pointer < undoState.stack.length - 1) {
|
||||||
_restore(undoState.stack[undoState.pointer + 1], setSelectedItems, onUpdateSvg);
|
const state = undoState.stack[undoState.pointer + 1];
|
||||||
dispatchPerformRedo();
|
_restore(state, setSelectedItems, onUpdateSvg);
|
||||||
|
const format = isVector(state.paintEditorFormat) ? Formats.UNDO_VECTOR :
|
||||||
|
isBitmap(state.paintEditorFormat) ? Formats.UNDO_BITMAP : null;
|
||||||
|
if (!format) {
|
||||||
|
log.error(`Invalid format: ${state.paintEditorFormat}`);
|
||||||
|
}
|
||||||
|
dispatchPerformRedo(format);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,22 @@ import keyMirror from 'keymirror';
|
||||||
|
|
||||||
const Formats = keyMirror({
|
const Formats = keyMirror({
|
||||||
BITMAP: null,
|
BITMAP: null,
|
||||||
VECTOR: null
|
VECTOR: null,
|
||||||
|
// Undo formats are conversions caused by the undo/redo stack
|
||||||
|
UNDO_BITMAP: null,
|
||||||
|
UNDO_VECTOR: null
|
||||||
});
|
});
|
||||||
|
|
||||||
export default Formats;
|
const isVector = function (format) {
|
||||||
|
return format === Formats.VECTOR || format === Formats.UNDO_VECTOR;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isBitmap = function (format) {
|
||||||
|
return format === Formats.BITMAP || format === Formats.UNDO_BITMAP;
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
Formats as default,
|
||||||
|
isVector,
|
||||||
|
isBitmap
|
||||||
|
};
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import Formats from '../lib/format';
|
import Formats from '../lib/format';
|
||||||
import log from '../log/log';
|
import log from '../log/log';
|
||||||
|
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 = Formats.VECTOR;
|
||||||
|
@ -7,6 +8,10 @@ const initialState = Formats.VECTOR;
|
||||||
const reducer = function (state, action) {
|
const reducer = function (state, action) {
|
||||||
if (typeof state === 'undefined') state = initialState;
|
if (typeof state === 'undefined') state = initialState;
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
case UNDO:
|
||||||
|
/* falls through */
|
||||||
|
case REDO:
|
||||||
|
/* falls through */
|
||||||
case CHANGE_FORMAT:
|
case CHANGE_FORMAT:
|
||||||
if (action.format in Formats) {
|
if (action.format in Formats) {
|
||||||
return action.format;
|
return action.format;
|
||||||
|
|
|
@ -63,14 +63,24 @@ const undoSnapshot = function (snapshot) {
|
||||||
snapshot: snapshot
|
snapshot: snapshot
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
const undo = function () {
|
/**
|
||||||
|
* @param {Format} format Either UNDO_VECTOR or UNDO_BITMAP
|
||||||
|
* @return {Action} undo action
|
||||||
|
*/
|
||||||
|
const undo = function (format) {
|
||||||
return {
|
return {
|
||||||
type: UNDO
|
type: UNDO,
|
||||||
|
format: format
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
const redo = function () {
|
/**
|
||||||
|
* @param {Format} format Either UNDO_VECTOR or UNDO_BITMAP
|
||||||
|
* @return {Action} undo action
|
||||||
|
*/
|
||||||
|
const redo = function (format) {
|
||||||
return {
|
return {
|
||||||
type: REDO
|
type: REDO,
|
||||||
|
format: format
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
const clearUndoState = function () {
|
const clearUndoState = function () {
|
||||||
|
@ -85,5 +95,7 @@ export {
|
||||||
redo,
|
redo,
|
||||||
undoSnapshot,
|
undoSnapshot,
|
||||||
clearUndoState,
|
clearUndoState,
|
||||||
MAX_STACK_SIZE
|
MAX_STACK_SIZE,
|
||||||
|
UNDO,
|
||||||
|
REDO
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import Formats from '../../src/lib/format';
|
import Formats from '../../src/lib/format';
|
||||||
import reducer from '../../src/reducers/format';
|
import reducer from '../../src/reducers/format';
|
||||||
import {changeFormat} from '../../src/reducers/format';
|
import {changeFormat} from '../../src/reducers/format';
|
||||||
|
import {undo, redo} from '../../src/reducers/undo';
|
||||||
|
|
||||||
test('initialState', () => {
|
test('initialState', () => {
|
||||||
let defaultState;
|
let defaultState;
|
||||||
|
@ -17,6 +18,16 @@ test('changeFormat', () => {
|
||||||
.toBe(Formats.VECTOR);
|
.toBe(Formats.VECTOR);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('undoRedoChangeFormat', () => {
|
||||||
|
let defaultState;
|
||||||
|
let reduxState = reducer(defaultState /* state */, changeFormat(Formats.BITMAP) /* action */);
|
||||||
|
expect(reduxState).toBe(Formats.BITMAP);
|
||||||
|
reduxState = reducer(reduxState /* state */, undo(Formats.UNDO_BITMAP) /* action */);
|
||||||
|
expect().toBe(Formats.UNDO_BITMAP);
|
||||||
|
reduxState = reducer(reduxState /* state */, redo(Formats.UNDO_VECTOR) /* action */);
|
||||||
|
expect().toBe(Formats.UNDO_VECTOR);
|
||||||
|
});
|
||||||
|
|
||||||
test('invalidChangeMode', () => {
|
test('invalidChangeMode', () => {
|
||||||
expect(reducer(Formats.BITMAP /* state */, changeFormat('non-existant mode') /* action */))
|
expect(reducer(Formats.BITMAP /* state */, changeFormat('non-existant mode') /* action */))
|
||||||
.toBe(Formats.BITMAP);
|
.toBe(Formats.BITMAP);
|
||||||
|
|
Loading…
Reference in a new issue