mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2024-12-23 05:52:42 -05:00
Add undo
This commit is contained in:
parent
86ee3d8cdd
commit
c75d3f63ba
3 changed files with 56 additions and 15 deletions
|
@ -7,6 +7,7 @@ import {changeMode} from '../reducers/modes';
|
||||||
import {undo, redo, undoSnapshot} from '../reducers/undo';
|
import {undo, redo, undoSnapshot} from '../reducers/undo';
|
||||||
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
|
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
|
||||||
import {deactivateEyeDropper} from '../reducers/eye-dropper';
|
import {deactivateEyeDropper} from '../reducers/eye-dropper';
|
||||||
|
import {setTextEditTarget} from '../reducers/text-edit-target';
|
||||||
|
|
||||||
import {hideGuideLayers, showGuideLayers} from '../helper/layer';
|
import {hideGuideLayers, showGuideLayers} from '../helper/layer';
|
||||||
import {performUndo, performRedo, performSnapshot, shouldShowUndo, shouldShowRedo} from '../helper/undo';
|
import {performUndo, performRedo, performSnapshot, shouldShowUndo, shouldShowRedo} from '../helper/undo';
|
||||||
|
@ -15,6 +16,7 @@ import {groupSelection, ungroupSelection} from '../helper/group';
|
||||||
import {getSelectedLeafItems} from '../helper/selection';
|
import {getSelectedLeafItems} from '../helper/selection';
|
||||||
import {resetZoom, zoomOnSelection} from '../helper/view';
|
import {resetZoom, zoomOnSelection} from '../helper/view';
|
||||||
import EyeDropperTool from '../helper/tools/eye-dropper';
|
import EyeDropperTool from '../helper/tools/eye-dropper';
|
||||||
|
import styles from '../components/paint-editor/paint-editor.css'
|
||||||
|
|
||||||
import Modes from '../lib/modes';
|
import Modes from '../lib/modes';
|
||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
|
@ -155,6 +157,12 @@ class PaintEditor extends React.Component {
|
||||||
document.activeElement.blur();
|
document.activeElement.blur();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.target !== paper.view.element && !event.target.classList.contains(styles.textArea)) {
|
||||||
|
debugger;
|
||||||
|
// Exit text edit mode if you click anywhere outside of canvas
|
||||||
|
this.props.removeTextEditTarget();
|
||||||
|
}
|
||||||
|
|
||||||
if (this.props.isEyeDropping) {
|
if (this.props.isEyeDropping) {
|
||||||
const colorString = this.eyeDropper.colorString;
|
const colorString = this.eyeDropper.colorString;
|
||||||
const callback = this.props.changeColorToEyeDropper;
|
const callback = this.props.changeColorToEyeDropper;
|
||||||
|
@ -303,6 +311,9 @@ const mapDispatchToProps = dispatch => ({
|
||||||
clearSelectedItems: () => {
|
clearSelectedItems: () => {
|
||||||
dispatch(clearSelectedItems());
|
dispatch(clearSelectedItems());
|
||||||
},
|
},
|
||||||
|
removeTextEditTarget: () => {
|
||||||
|
dispatch(setTextEditTarget());
|
||||||
|
},
|
||||||
setSelectedItems: () => {
|
setSelectedItems: () => {
|
||||||
dispatch(setSelectedItems(getSelectedLeafItems()));
|
dispatch(setSelectedItems(getSelectedLeafItems()));
|
||||||
},
|
},
|
||||||
|
|
|
@ -36,6 +36,9 @@ class TextMode extends React.Component {
|
||||||
if (this.tool && nextProps.selectedItems !== this.props.selectedItems) {
|
if (this.tool && nextProps.selectedItems !== this.props.selectedItems) {
|
||||||
this.tool.onSelectionChanged(nextProps.selectedItems);
|
this.tool.onSelectionChanged(nextProps.selectedItems);
|
||||||
}
|
}
|
||||||
|
if (this.tool && !nextProps.textEditTarget && this.props.textEditTarget) {
|
||||||
|
this.tool.onTextEditCancelled();
|
||||||
|
}
|
||||||
|
|
||||||
if (nextProps.isTextModeActive && !this.props.isTextModeActive) {
|
if (nextProps.isTextModeActive && !this.props.isTextModeActive) {
|
||||||
this.activateTool();
|
this.activateTool();
|
||||||
|
@ -103,13 +106,15 @@ TextMode.propTypes = {
|
||||||
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,
|
||||||
textArea: PropTypes.instanceOf(Element)
|
textArea: PropTypes.instanceOf(Element),
|
||||||
|
textEditTarget: PropTypes.number
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
colorState: state.scratchPaint.color,
|
colorState: state.scratchPaint.color,
|
||||||
isTextModeActive: state.scratchPaint.mode === Modes.TEXT,
|
isTextModeActive: state.scratchPaint.mode === Modes.TEXT,
|
||||||
selectedItems: state.scratchPaint.selectedItems
|
selectedItems: state.scratchPaint.selectedItems,
|
||||||
|
textEditTarget: state.scratchPaint.textEditTarget
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
clearSelectedItems: () => {
|
clearSelectedItems: () => {
|
||||||
|
|
|
@ -24,6 +24,10 @@ class TextTool extends paper.Tool {
|
||||||
static get DOUBLE_CLICK_MILLIS () {
|
static get DOUBLE_CLICK_MILLIS () {
|
||||||
return 250;
|
return 250;
|
||||||
}
|
}
|
||||||
|
/** Typing with no pauses longer than this amount of type will count as 1 action */
|
||||||
|
static get TYPING_TIMEOUT_MILLIS () {
|
||||||
|
return 1000;
|
||||||
|
}
|
||||||
static get TEXT_PADDING () {
|
static get TEXT_PADDING () {
|
||||||
return 8;
|
return 8;
|
||||||
}
|
}
|
||||||
|
@ -59,6 +63,7 @@ class TextTool extends paper.Tool {
|
||||||
this.colorState = null;
|
this.colorState = null;
|
||||||
this.mode = null;
|
this.mode = null;
|
||||||
this.active = false;
|
this.active = false;
|
||||||
|
this.lastTypeEvent = null;
|
||||||
|
|
||||||
// If text selected and then activate this tool, switch to text edit mode for that text
|
// If text selected and then activate this tool, switch to text edit mode for that text
|
||||||
// If double click on text while in select mode, does mode change to text mode? Text fully selected by default
|
// If double click on text while in select mode, does mode change to text mode? Text fully selected by default
|
||||||
|
@ -95,6 +100,10 @@ class TextTool extends paper.Tool {
|
||||||
onSelectionChanged (selectedItems) {
|
onSelectionChanged (selectedItems) {
|
||||||
this.boundingBoxTool.onSelectionChanged(selectedItems);
|
this.boundingBoxTool.onSelectionChanged(selectedItems);
|
||||||
}
|
}
|
||||||
|
// Allow other tools to cancel text edit mode
|
||||||
|
onTextEditCancelled () {
|
||||||
|
this.endTextEdit();
|
||||||
|
}
|
||||||
setColorState (colorState) {
|
setColorState (colorState) {
|
||||||
this.colorState = colorState;
|
this.colorState = colorState;
|
||||||
}
|
}
|
||||||
|
@ -110,12 +119,6 @@ class TextTool extends paper.Tool {
|
||||||
if (event.event.button > 0) return; // only first mouse button
|
if (event.event.button > 0) return; // only first mouse button
|
||||||
this.active = true;
|
this.active = true;
|
||||||
|
|
||||||
// Remove invisible textboxes
|
|
||||||
if (this.textBox && this.textBox.content.trim() === '') {
|
|
||||||
this.textBox.remove();
|
|
||||||
this.textBox = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if double clicked
|
// Check if double clicked
|
||||||
let doubleClicked = false;
|
let doubleClicked = false;
|
||||||
if (this.lastEvent) {
|
if (this.lastEvent) {
|
||||||
|
@ -142,10 +145,12 @@ class TextTool extends paper.Tool {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const lastMode = this.mode;
|
||||||
// We clicked away from the item, so end the current mode
|
// We clicked away from the item, so end the current mode
|
||||||
if (this.mode === TextTool.SELECT_MODE) {
|
if (lastMode === TextTool.SELECT_MODE) {
|
||||||
clearSelection(this.clearSelectedItems);
|
clearSelection(this.clearSelectedItems);
|
||||||
} else if (this.mode === TextTool.TEXT_EDIT_MODE) {
|
this.mode = null;
|
||||||
|
} else if (lastMode === TextTool.TEXT_EDIT_MODE) {
|
||||||
this.endTextEdit();
|
this.endTextEdit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,14 +159,12 @@ class TextTool extends paper.Tool {
|
||||||
// Clicking a different text item to begin text edit mode on that item
|
// Clicking a different text item to begin text edit mode on that item
|
||||||
this.textBox = hitResults[0].item;
|
this.textBox = hitResults[0].item;
|
||||||
this.beginTextEdit(this.textBox.content, this.textBox.matrix);
|
this.beginTextEdit(this.textBox.content, this.textBox.matrix);
|
||||||
} else if (this.mode === TextTool.TEXT_EDIT_MODE) {
|
} else if (lastMode === TextTool.TEXT_EDIT_MODE) {
|
||||||
// In text mode clicking away to begin select mode
|
// In text mode clicking away to begin select mode
|
||||||
if (this.textBox) {
|
if (this.textBox) {
|
||||||
this.mode = TextTool.SELECT_MODE;
|
this.mode = TextTool.SELECT_MODE;
|
||||||
this.textBox.selected = true;
|
this.textBox.selected = true;
|
||||||
this.setSelectedItems();
|
this.setSelectedItems();
|
||||||
} else {
|
|
||||||
this.mode = null;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// In no mode or select mode clicking away to begin text edit mode
|
// In no mode or select mode clicking away to begin text edit mode
|
||||||
|
@ -214,7 +217,12 @@ class TextTool extends paper.Tool {
|
||||||
this.nudgeTool.onKeyUp(event);
|
this.nudgeTool.onKeyUp(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handleTextInput () {
|
handleTextInput (event) {
|
||||||
|
// Save undo state if you paused typing for long enough.
|
||||||
|
if (this.lastTypeEvent && event.timeStamp - this.lastTypeEvent.timeStamp > TextTool.TYPING_TIMEOUT_MILLIS) {
|
||||||
|
this.onUpdateSvg();
|
||||||
|
}
|
||||||
|
this.lastTypeEvent = event;
|
||||||
if (this.mode === TextTool.TEXT_EDIT_MODE) {
|
if (this.mode === TextTool.TEXT_EDIT_MODE) {
|
||||||
this.textBox.content = this.element.value;
|
this.textBox.content = this.element.value;
|
||||||
}
|
}
|
||||||
|
@ -237,7 +245,6 @@ class TextTool extends paper.Tool {
|
||||||
this.setTextEditTarget(this.textBox.id);
|
this.setTextEditTarget(this.textBox.id);
|
||||||
|
|
||||||
// TODO text not positioned correctly when zoomed in
|
// TODO text not positioned correctly when zoomed in
|
||||||
// TODO holding shift when rotating in select mode does weird things
|
|
||||||
// TODO undo states
|
// TODO undo states
|
||||||
|
|
||||||
this.element.style.display = 'initial';
|
this.element.style.display = 'initial';
|
||||||
|
@ -259,6 +266,18 @@ class TextTool extends paper.Tool {
|
||||||
this.resizeGuide();
|
this.resizeGuide();
|
||||||
}
|
}
|
||||||
endTextEdit () {
|
endTextEdit () {
|
||||||
|
if (this.mode !== TextTool.TEXT_EDIT_MODE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.mode = null;
|
||||||
|
|
||||||
|
// Remove invisible textboxes
|
||||||
|
if (this.textBox && this.textBox.content.trim() === '') {
|
||||||
|
this.textBox.remove();
|
||||||
|
this.textBox = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove guide
|
||||||
if (this.guide) {
|
if (this.guide) {
|
||||||
this.guide.remove();
|
this.guide.remove();
|
||||||
this.guide = null;
|
this.guide = null;
|
||||||
|
@ -269,6 +288,12 @@ class TextTool extends paper.Tool {
|
||||||
this.element.removeEventListener('input', this.eventListener);
|
this.element.removeEventListener('input', this.eventListener);
|
||||||
this.eventListener = null;
|
this.eventListener = null;
|
||||||
}
|
}
|
||||||
|
this.lastTypeEvent = null;
|
||||||
|
|
||||||
|
// If you finished editing a textbox, save undo state
|
||||||
|
if (this.textBox && this.textBox.content.trim().length) {
|
||||||
|
this.onUpdateSvg();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
deactivateTool () {
|
deactivateTool () {
|
||||||
if (this.textBox && this.textBox.content.trim() === '') {
|
if (this.textBox && this.textBox.content.trim() === '') {
|
||||||
|
|
Loading…
Reference in a new issue