mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2025-01-10 14:42:13 -05:00
Add a text edit area
This commit is contained in:
parent
cbd2a89cd0
commit
da0864b81b
4 changed files with 80 additions and 29 deletions
15
src/components/text-mode/text-mode.css
Normal file
15
src/components/text-mode/text-mode.css
Normal file
|
@ -0,0 +1,15 @@
|
|||
.text-area {
|
||||
background: transparent;
|
||||
border: none;
|
||||
display: none;
|
||||
font-family: Times;
|
||||
font-size: 30px;
|
||||
left: 0;
|
||||
outline: none;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
resize: none;
|
||||
top: 0;
|
||||
white-space: nowrap;
|
||||
z-index: 1;
|
||||
}
|
|
@ -3,23 +3,32 @@ import PropTypes from 'prop-types';
|
|||
import ToolSelectComponent from '../tool-select-base/tool-select-base.jsx';
|
||||
|
||||
import textIcon from './text.svg';
|
||||
import styles from './text-mode.css';
|
||||
|
||||
const TextModeComponent = props => (
|
||||
<ToolSelectComponent
|
||||
imgDescriptor={{
|
||||
defaultMessage: 'Text',
|
||||
description: 'Label for the text tool',
|
||||
id: 'paint.textMode.text'
|
||||
}}
|
||||
imgSrc={textIcon}
|
||||
isSelected={props.isSelected}
|
||||
onMouseDown={props.onMouseDown}
|
||||
/>
|
||||
<div>
|
||||
<ToolSelectComponent
|
||||
imgDescriptor={{
|
||||
defaultMessage: 'Text',
|
||||
description: 'Label for the text tool',
|
||||
id: 'paint.textMode.text'
|
||||
}}
|
||||
imgSrc={textIcon}
|
||||
isSelected={props.isSelected}
|
||||
onMouseDown={props.onMouseDown}
|
||||
/>
|
||||
|
||||
<textarea
|
||||
className={styles.textArea}
|
||||
ref={props.setTextArea}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
TextModeComponent.propTypes = {
|
||||
isSelected: PropTypes.bool.isRequired,
|
||||
onMouseDown: PropTypes.func.isRequired
|
||||
onMouseDown: PropTypes.func.isRequired,
|
||||
setTextArea: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default TextModeComponent;
|
||||
|
|
|
@ -21,7 +21,8 @@ class TextMode extends React.Component {
|
|||
super(props);
|
||||
bindAll(this, [
|
||||
'activateTool',
|
||||
'deactivateTool'
|
||||
'deactivateTool',
|
||||
'setTextArea'
|
||||
]);
|
||||
}
|
||||
componentDidMount () {
|
||||
|
@ -64,6 +65,7 @@ class TextMode extends React.Component {
|
|||
this.props.onChangeStrokeColor(null);
|
||||
}
|
||||
this.tool = new TextTool(
|
||||
this.textArea,
|
||||
this.props.setSelectedItems,
|
||||
this.props.clearSelectedItems,
|
||||
this.props.onUpdateSvg,
|
||||
|
@ -77,11 +79,15 @@ class TextMode extends React.Component {
|
|||
this.tool.remove();
|
||||
this.tool = null;
|
||||
}
|
||||
setTextArea (element) {
|
||||
this.textArea = element;
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<TextModeComponent
|
||||
isSelected={this.props.isTextModeActive}
|
||||
onMouseDown={this.props.handleMouseDown}
|
||||
setTextArea={this.setTextArea}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -28,13 +28,15 @@ class TextTool extends paper.Tool {
|
|||
return 8;
|
||||
}
|
||||
/**
|
||||
* @param {HTMLTextAreaElement} textAreaElement dom element for the editable text field
|
||||
* @param {function} setSelectedItems Callback to set the set of selected items in the Redux state
|
||||
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
|
||||
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
||||
* @param {!function} setTextEditTarget Call to set text editing target whenever text editing is active
|
||||
*/
|
||||
constructor (setSelectedItems, clearSelectedItems, onUpdateSvg, setTextEditTarget) {
|
||||
constructor (textAreaElement, setSelectedItems, clearSelectedItems, onUpdateSvg, setTextEditTarget) {
|
||||
super();
|
||||
this.element = textAreaElement;
|
||||
this.setSelectedItems = setSelectedItems;
|
||||
this.clearSelectedItems = clearSelectedItems;
|
||||
this.onUpdateSvg = onUpdateSvg;
|
||||
|
@ -141,10 +143,6 @@ class TextTool extends paper.Tool {
|
|||
const hitResults = paper.project.hitTestAll(event.point, this.getTextEditHitOptions());
|
||||
if (hitResults.length) {
|
||||
// Clicking a text item to begin text edit mode on that item
|
||||
if (this.guide) {
|
||||
this.guide.remove();
|
||||
this.guide = null;
|
||||
}
|
||||
this.textBox = hitResults[0].item;
|
||||
this.mode = TextTool.TEXT_EDIT_MODE;
|
||||
} else if (this.mode === TextTool.TEXT_EDIT_MODE) {
|
||||
|
@ -171,13 +169,9 @@ class TextTool extends paper.Tool {
|
|||
}
|
||||
|
||||
if (this.mode === TextTool.TEXT_EDIT_MODE) {
|
||||
this.guide = hoverBounds(this.textBox, TextTool.TEXT_PADDING);
|
||||
this.guide.dashArray = [4, 4];
|
||||
this.setTextEditTarget(this.textBox.id);
|
||||
} else if (this.guide) {
|
||||
this.guide.remove();
|
||||
this.guide = null;
|
||||
this.setTextEditTarget();
|
||||
this.beginTextEdit(event.point);
|
||||
} else {
|
||||
this.endTextEdit();
|
||||
}
|
||||
}
|
||||
handleMouseDrag (event) {
|
||||
|
@ -224,17 +218,44 @@ class TextTool extends paper.Tool {
|
|||
this.guide.dashArray = [4, 4];
|
||||
}
|
||||
}
|
||||
beginTextEdit (location) {
|
||||
if (this.guide) {
|
||||
this.guide.remove();
|
||||
}
|
||||
|
||||
this.guide = hoverBounds(this.textBox, TextTool.TEXT_PADDING);
|
||||
this.guide.dashArray = [4, 4];
|
||||
this.setTextEditTarget(this.textBox.id);
|
||||
|
||||
const canvasRect = paper.view.element.getBoundingClientRect();
|
||||
|
||||
this.element.style.display = 'initial';
|
||||
this.element.style.text = 'hello';
|
||||
this.element.style['text-fill-color'] = this.colorState.fillColor;
|
||||
this.element.style['text-stroke-color'] = this.colorState.strokeColor;
|
||||
this.element.style['text-stroke-width'] = this.colorState.strokeWidth;
|
||||
this.element.style['-webkit-text-fill-color'] = this.colorState.fillColor;
|
||||
this.element.style['-webkit-text-stroke-color'] = this.colorState.strokeColor;
|
||||
this.element.style['-webkit-text-stroke-width'] = this.colorState.strokeWidth + 'px';
|
||||
this.element.style.transform =
|
||||
`translate(${location.x + canvasRect.x}px, ${location.y + canvasRect.y}px)`;
|
||||
this.element.focus();
|
||||
}
|
||||
endTextEdit () {
|
||||
if (this.guide) {
|
||||
this.guide.remove();
|
||||
this.guide = null;
|
||||
this.setTextEditTarget();
|
||||
}
|
||||
this.element.style.display = 'none';
|
||||
}
|
||||
deactivateTool () {
|
||||
this.boundingBoxTool.removeBoundsPath();
|
||||
if (this.textBox && this.textBox.content.trim() === '') {
|
||||
this.textBox.remove();
|
||||
this.textBox = null;
|
||||
}
|
||||
if (this.guide) {
|
||||
this.guide.remove();
|
||||
this.guide = null;
|
||||
this.setTextEditTarget();
|
||||
}
|
||||
this.endTextEdit();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue