diff --git a/src/containers/paint-editor.jsx b/src/containers/paint-editor.jsx index bea65d92..f3bce656 100644 --- a/src/containers/paint-editor.jsx +++ b/src/containers/paint-editor.jsx @@ -7,6 +7,7 @@ import {changeMode} from '../reducers/modes'; import {undo, redo, undoSnapshot} from '../reducers/undo'; import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items'; import {deactivateEyeDropper} from '../reducers/eye-dropper'; +import {setTextEditTarget} from '../reducers/text-edit-target'; import {hideGuideLayers, showGuideLayers} from '../helper/layer'; 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 {resetZoom, zoomOnSelection} from '../helper/view'; import EyeDropperTool from '../helper/tools/eye-dropper'; +import styles from '../components/paint-editor/paint-editor.css' import Modes from '../lib/modes'; import {connect} from 'react-redux'; @@ -155,6 +157,12 @@ class PaintEditor extends React.Component { 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) { const colorString = this.eyeDropper.colorString; const callback = this.props.changeColorToEyeDropper; @@ -303,6 +311,9 @@ const mapDispatchToProps = dispatch => ({ clearSelectedItems: () => { dispatch(clearSelectedItems()); }, + removeTextEditTarget: () => { + dispatch(setTextEditTarget()); + }, setSelectedItems: () => { dispatch(setSelectedItems(getSelectedLeafItems())); }, diff --git a/src/containers/text-mode.jsx b/src/containers/text-mode.jsx index 0e7ab70f..bbc21a2a 100644 --- a/src/containers/text-mode.jsx +++ b/src/containers/text-mode.jsx @@ -36,6 +36,9 @@ class TextMode extends React.Component { if (this.tool && nextProps.selectedItems !== this.props.selectedItems) { this.tool.onSelectionChanged(nextProps.selectedItems); } + if (this.tool && !nextProps.textEditTarget && this.props.textEditTarget) { + this.tool.onTextEditCancelled(); + } if (nextProps.isTextModeActive && !this.props.isTextModeActive) { this.activateTool(); @@ -103,13 +106,15 @@ TextMode.propTypes = { selectedItems: PropTypes.arrayOf(PropTypes.instanceOf(paper.Item)), setSelectedItems: PropTypes.func.isRequired, setTextEditTarget: PropTypes.func.isRequired, - textArea: PropTypes.instanceOf(Element) + textArea: PropTypes.instanceOf(Element), + textEditTarget: PropTypes.number }; const mapStateToProps = state => ({ colorState: state.scratchPaint.color, isTextModeActive: state.scratchPaint.mode === Modes.TEXT, - selectedItems: state.scratchPaint.selectedItems + selectedItems: state.scratchPaint.selectedItems, + textEditTarget: state.scratchPaint.textEditTarget }); const mapDispatchToProps = dispatch => ({ clearSelectedItems: () => { diff --git a/src/helper/tools/text-tool.js b/src/helper/tools/text-tool.js index 76950a51..70e174ba 100644 --- a/src/helper/tools/text-tool.js +++ b/src/helper/tools/text-tool.js @@ -24,6 +24,10 @@ class TextTool extends paper.Tool { static get DOUBLE_CLICK_MILLIS () { 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 () { return 8; } @@ -59,6 +63,7 @@ class TextTool extends paper.Tool { this.colorState = null; this.mode = null; this.active = false; + this.lastTypeEvent = null; // 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 @@ -95,6 +100,10 @@ class TextTool extends paper.Tool { onSelectionChanged (selectedItems) { this.boundingBoxTool.onSelectionChanged(selectedItems); } + // Allow other tools to cancel text edit mode + onTextEditCancelled () { + this.endTextEdit(); + } setColorState (colorState) { this.colorState = colorState; } @@ -110,12 +119,6 @@ class TextTool extends paper.Tool { if (event.event.button > 0) return; // only first mouse button this.active = true; - // Remove invisible textboxes - if (this.textBox && this.textBox.content.trim() === '') { - this.textBox.remove(); - this.textBox = null; - } - // Check if double clicked let doubleClicked = false; if (this.lastEvent) { @@ -142,10 +145,12 @@ class TextTool extends paper.Tool { return; } + const lastMode = this.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); - } else if (this.mode === TextTool.TEXT_EDIT_MODE) { + this.mode = null; + } else if (lastMode === TextTool.TEXT_EDIT_MODE) { this.endTextEdit(); } @@ -154,14 +159,12 @@ class TextTool extends paper.Tool { // Clicking a different text item to begin text edit mode on that item this.textBox = hitResults[0].item; 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 if (this.textBox) { this.mode = TextTool.SELECT_MODE; this.textBox.selected = true; this.setSelectedItems(); - } else { - this.mode = null; } } else { // 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); } } - 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) { this.textBox.content = this.element.value; } @@ -237,7 +245,6 @@ class TextTool extends paper.Tool { this.setTextEditTarget(this.textBox.id); // TODO text not positioned correctly when zoomed in - // TODO holding shift when rotating in select mode does weird things // TODO undo states this.element.style.display = 'initial'; @@ -259,6 +266,18 @@ class TextTool extends paper.Tool { this.resizeGuide(); } 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) { this.guide.remove(); this.guide = null; @@ -269,6 +288,12 @@ class TextTool extends paper.Tool { this.element.removeEventListener('input', this.eventListener); 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 () { if (this.textBox && this.textBox.content.trim() === '') {