mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2024-12-23 05:52:42 -05:00
parent
7f216defd2
commit
0240abcfe3
12 changed files with 553 additions and 8 deletions
|
@ -13,7 +13,8 @@ class Dropdown extends React.Component {
|
||||||
super(props);
|
super(props);
|
||||||
bindAll(this, [
|
bindAll(this, [
|
||||||
'handleClosePopover',
|
'handleClosePopover',
|
||||||
'handleToggleOpenState'
|
'handleToggleOpenState',
|
||||||
|
'isOpen'
|
||||||
]);
|
]);
|
||||||
this.state = {
|
this.state = {
|
||||||
isOpen: false
|
isOpen: false
|
||||||
|
@ -25,9 +26,16 @@ class Dropdown extends React.Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
handleToggleOpenState () {
|
handleToggleOpenState () {
|
||||||
|
const newState = !this.state.isOpen;
|
||||||
this.setState({
|
this.setState({
|
||||||
isOpen: !this.state.isOpen
|
isOpen: newState
|
||||||
});
|
});
|
||||||
|
if (newState && this.props.onOpen) {
|
||||||
|
this.props.onOpen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isOpen () {
|
||||||
|
return this.state.isOpen;
|
||||||
}
|
}
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
|
@ -35,7 +43,8 @@ class Dropdown extends React.Component {
|
||||||
body={this.props.popoverContent}
|
body={this.props.popoverContent}
|
||||||
isOpen={this.state.isOpen}
|
isOpen={this.state.isOpen}
|
||||||
preferPlace="below"
|
preferPlace="below"
|
||||||
onOuterAction={this.handleClosePopover}
|
onOuterAction={this.props.onOuterAction ?
|
||||||
|
this.props.onOuterAction : this.handleClosePopover}
|
||||||
{...this.props}
|
{...this.props}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
@ -62,6 +71,8 @@ class Dropdown extends React.Component {
|
||||||
Dropdown.propTypes = {
|
Dropdown.propTypes = {
|
||||||
children: PropTypes.node.isRequired,
|
children: PropTypes.node.isRequired,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
|
onOpen: PropTypes.func,
|
||||||
|
onOuterAction: PropTypes.func,
|
||||||
popoverContent: PropTypes.node.isRequired
|
popoverContent: PropTypes.node.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
80
src/components/font-dropdown/font-dropdown.css
Normal file
80
src/components/font-dropdown/font-dropdown.css
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
@import "../../css/colors.css";
|
||||||
|
@import "../../css/units.css";
|
||||||
|
|
||||||
|
.mod-menu-item {
|
||||||
|
display: flex;
|
||||||
|
margin: 0 -$grid-unit;
|
||||||
|
min-width: 6.25rem;
|
||||||
|
padding: calc(2 * $grid-unit);
|
||||||
|
padding-left: calc(3 * $grid-unit);
|
||||||
|
padding-right: calc(3 * $grid-unit);
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 8.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: 0.1s ease;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mod-menu-item:hover {
|
||||||
|
background: $motion-primary;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mod-context-menu {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mod-unselect {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-dropdown {
|
||||||
|
align-items: center;
|
||||||
|
color: $text-primary;
|
||||||
|
display: flex;
|
||||||
|
font-size: 0.625rem;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 8.5rem;
|
||||||
|
height: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.serif {
|
||||||
|
font-family: 'Serif';
|
||||||
|
}
|
||||||
|
|
||||||
|
.sans-serif {
|
||||||
|
font-family: 'Sans Serif';
|
||||||
|
}
|
||||||
|
|
||||||
|
.serif {
|
||||||
|
font-family: 'Serif';
|
||||||
|
}
|
||||||
|
|
||||||
|
.handwriting {
|
||||||
|
font-family: 'Handwriting';
|
||||||
|
}
|
||||||
|
|
||||||
|
.marker {
|
||||||
|
font-family: 'Marker';
|
||||||
|
}
|
||||||
|
|
||||||
|
.curly {
|
||||||
|
font-family: 'Curly';
|
||||||
|
}
|
||||||
|
|
||||||
|
.pixel {
|
||||||
|
font-family: 'Pixel';
|
||||||
|
}
|
||||||
|
|
||||||
|
.chinese {
|
||||||
|
font-family: "Microsoft YaHei", "微软雅黑", STXihei, "华文细黑";
|
||||||
|
}
|
||||||
|
|
||||||
|
.japanese {
|
||||||
|
font-family: "ヒラギノ角ゴ Pro W3", "Hiragino Kaku Gothic Pro", Osaka, "メイリオ", Meiryo, "MS Pゴシック", "MS PGothic";
|
||||||
|
}
|
||||||
|
|
||||||
|
.korean {
|
||||||
|
font-family: "Malgun Gothic";
|
||||||
|
}
|
129
src/components/font-dropdown/font-dropdown.jsx
Normal file
129
src/components/font-dropdown/font-dropdown.jsx
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Button from '../button/button.jsx';
|
||||||
|
import Dropdown from '../dropdown/dropdown.jsx';
|
||||||
|
import InputGroup from '../input-group/input-group.jsx';
|
||||||
|
import Fonts from '../../lib/fonts';
|
||||||
|
import styles from './font-dropdown.css';
|
||||||
|
|
||||||
|
const ModeToolsComponent = props => (
|
||||||
|
<Dropdown
|
||||||
|
className={classNames(styles.modUnselect, styles.fontDropdown)}
|
||||||
|
enterExitTransitionDurationMs={60}
|
||||||
|
popoverContent={
|
||||||
|
<InputGroup className={styles.modContextMenu}>
|
||||||
|
<Button
|
||||||
|
className={classNames(styles.modMenuItem)}
|
||||||
|
onClick={props.onChoose}
|
||||||
|
onMouseOver={props.onHoverSansSerif}
|
||||||
|
>
|
||||||
|
<span className={styles.sansSerif}>
|
||||||
|
{props.getTranslatedFontName(Fonts.SANS_SERIF)}
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className={classNames(styles.modMenuItem)}
|
||||||
|
onClick={props.onChoose}
|
||||||
|
onMouseOver={props.onHoverSerif}
|
||||||
|
>
|
||||||
|
<span className={styles.serif}>
|
||||||
|
{props.getTranslatedFontName(Fonts.SERIF)}
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className={classNames(styles.modMenuItem)}
|
||||||
|
onClick={props.onChoose}
|
||||||
|
onMouseOver={props.onHoverHandwriting}
|
||||||
|
>
|
||||||
|
<span className={styles.handwriting}>
|
||||||
|
{props.getTranslatedFontName(Fonts.HANDWRITING)}
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className={classNames(styles.modMenuItem)}
|
||||||
|
onClick={props.onChoose}
|
||||||
|
onMouseOver={props.onHoverMarker}
|
||||||
|
>
|
||||||
|
<span className={styles.marker}>
|
||||||
|
{props.getTranslatedFontName(Fonts.MARKER)}
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className={classNames(styles.modMenuItem)}
|
||||||
|
onClick={props.onChoose}
|
||||||
|
onMouseOver={props.onHoverCurly}
|
||||||
|
>
|
||||||
|
<span className={styles.curly}>
|
||||||
|
{props.getTranslatedFontName(Fonts.CURLY)}
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className={classNames(styles.modMenuItem)}
|
||||||
|
onClick={props.onChoose}
|
||||||
|
onMouseOver={props.onHoverPixel}
|
||||||
|
>
|
||||||
|
<span className={styles.pixel}>
|
||||||
|
{props.getTranslatedFontName(Fonts.PIXEL)}
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className={classNames(styles.modMenuItem)}
|
||||||
|
onClick={props.onChoose}
|
||||||
|
onMouseOver={props.onHoverChinese}
|
||||||
|
>
|
||||||
|
<span className={styles.chinese}>
|
||||||
|
{props.getTranslatedFontName(Fonts.CHINESE)}
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className={classNames(styles.modMenuItem)}
|
||||||
|
onClick={props.onChoose}
|
||||||
|
onMouseOver={props.onHoverJapanese}
|
||||||
|
>
|
||||||
|
<span className={styles.japanese}>
|
||||||
|
{props.getTranslatedFontName(Fonts.JAPANESE)}
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className={classNames(styles.modMenuItem)}
|
||||||
|
onClick={props.onChoose}
|
||||||
|
onMouseOver={props.onHoverKorean}
|
||||||
|
>
|
||||||
|
<span className={styles.korean}>
|
||||||
|
{props.getTranslatedFontName(Fonts.KOREAN)}
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
</InputGroup>
|
||||||
|
}
|
||||||
|
ref={props.componentRef}
|
||||||
|
tipSize={.01}
|
||||||
|
onOpen={props.onOpenDropdown}
|
||||||
|
onOuterAction={props.onClickOutsideDropdown}
|
||||||
|
>
|
||||||
|
<span className={props.getFontStyle(props.font)}>
|
||||||
|
{props.getTranslatedFontName(props.font)}
|
||||||
|
</span>
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
|
|
||||||
|
ModeToolsComponent.propTypes = {
|
||||||
|
componentRef: PropTypes.func.isRequired,
|
||||||
|
font: PropTypes.string,
|
||||||
|
getFontStyle: PropTypes.func.isRequired,
|
||||||
|
getTranslatedFontName: PropTypes.func.isRequired,
|
||||||
|
onChoose: PropTypes.func.isRequired,
|
||||||
|
onClickOutsideDropdown: PropTypes.func,
|
||||||
|
onHoverChinese: PropTypes.func,
|
||||||
|
onHoverCurly: PropTypes.func,
|
||||||
|
onHoverHandwriting: PropTypes.func,
|
||||||
|
onHoverJapanese: PropTypes.func,
|
||||||
|
onHoverKorean: PropTypes.func,
|
||||||
|
onHoverMarker: PropTypes.func,
|
||||||
|
onHoverPixel: PropTypes.func,
|
||||||
|
onHoverSansSerif: PropTypes.func,
|
||||||
|
onHoverSerif: PropTypes.func,
|
||||||
|
onOpenDropdown: PropTypes.func
|
||||||
|
};
|
||||||
|
export default ModeToolsComponent;
|
|
@ -8,6 +8,7 @@ import {changeBrushSize} from '../../reducers/brush-mode';
|
||||||
import {changeBrushSize as changeEraserSize} from '../../reducers/eraser-mode';
|
import {changeBrushSize as changeEraserSize} from '../../reducers/eraser-mode';
|
||||||
import {changeBitBrushSize} from '../../reducers/bit-brush-size';
|
import {changeBitBrushSize} from '../../reducers/bit-brush-size';
|
||||||
|
|
||||||
|
import FontDropdown from '../../containers/font-dropdown.jsx';
|
||||||
import LiveInputHOC from '../forms/live-input-hoc.jsx';
|
import LiveInputHOC from '../forms/live-input-hoc.jsx';
|
||||||
import {defineMessages, injectIntl, intlShape} from 'react-intl';
|
import {defineMessages, injectIntl, intlShape} from 'react-intl';
|
||||||
import Input from '../forms/input.jsx';
|
import Input from '../forms/input.jsx';
|
||||||
|
@ -186,6 +187,16 @@ const ModeToolsComponent = props => {
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
case Modes.TEXT:
|
||||||
|
return (
|
||||||
|
<div className={classNames(props.className, styles.modeTools)}>
|
||||||
|
<InputGroup>
|
||||||
|
<FontDropdown
|
||||||
|
onUpdateImage={props.onUpdateImage}
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
// Leave empty for now, if mode not supported
|
// Leave empty for now, if mode not supported
|
||||||
return (
|
return (
|
||||||
|
@ -214,6 +225,7 @@ ModeToolsComponent.propTypes = {
|
||||||
onFlipVertical: PropTypes.func.isRequired,
|
onFlipVertical: PropTypes.func.isRequired,
|
||||||
onPasteFromClipboard: PropTypes.func.isRequired,
|
onPasteFromClipboard: PropTypes.func.isRequired,
|
||||||
onPointPoints: PropTypes.func.isRequired,
|
onPointPoints: PropTypes.func.isRequired,
|
||||||
|
onUpdateImage: PropTypes.func.isRequired,
|
||||||
selectedItems: PropTypes.arrayOf(PropTypes.instanceOf(paper.Item))
|
selectedItems: PropTypes.arrayOf(PropTypes.instanceOf(paper.Item))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -168,8 +168,6 @@ $border-radius: 0.25rem;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
display: none;
|
display: none;
|
||||||
font-family: Helvetica;
|
|
||||||
font-size: 30px;
|
|
||||||
outline: none;
|
outline: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
|
|
231
src/containers/font-dropdown.jsx
Normal file
231
src/containers/font-dropdown.jsx
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
import paper from '@scratch/paper';
|
||||||
|
import {connect} from 'react-redux';
|
||||||
|
import bindAll from 'lodash.bindall';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import {defineMessages, injectIntl, intlShape} from 'react-intl';
|
||||||
|
|
||||||
|
import FontDropdownComponent from '../components/font-dropdown/font-dropdown.jsx';
|
||||||
|
import Fonts from '../lib/fonts';
|
||||||
|
import {changeFont} from '../reducers/font';
|
||||||
|
import {getSelectedLeafItems} from '../helper/selection';
|
||||||
|
import styles from '../components/font-dropdown/font-dropdown.css';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
sansSerif: {
|
||||||
|
defaultMessage: 'Sans Serif',
|
||||||
|
description: 'Name of the sans serif font',
|
||||||
|
id: 'paint.modeTools.sansSerif'
|
||||||
|
},
|
||||||
|
serif: {
|
||||||
|
defaultMessage: 'Serif',
|
||||||
|
description: 'Name of the serif font',
|
||||||
|
id: 'paint.modeTools.serif'
|
||||||
|
},
|
||||||
|
handwriting: {
|
||||||
|
defaultMessage: 'Handwriting',
|
||||||
|
description: 'Name of the handwriting font',
|
||||||
|
id: 'paint.modeTools.handwriting'
|
||||||
|
},
|
||||||
|
marker: {
|
||||||
|
defaultMessage: 'Marker',
|
||||||
|
description: 'Name of the marker font',
|
||||||
|
id: 'paint.modeTools.marker'
|
||||||
|
},
|
||||||
|
curly: {
|
||||||
|
defaultMessage: 'Curly',
|
||||||
|
description: 'Name of the curly font',
|
||||||
|
id: 'paint.modeTools.curly'
|
||||||
|
},
|
||||||
|
pixel: {
|
||||||
|
defaultMessage: 'Pixel',
|
||||||
|
description: 'Name of the pixelated font',
|
||||||
|
id: 'paint.modeTools.pixel'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
class ModeToolsComponent extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props);
|
||||||
|
bindAll(this, [
|
||||||
|
'getFontStyle',
|
||||||
|
'getTranslatedFontName',
|
||||||
|
'handleChangeFontSerif',
|
||||||
|
'handleChangeFontSansSerif',
|
||||||
|
'handleChangeFontHandwriting',
|
||||||
|
'handleChangeFontMarker',
|
||||||
|
'handleChangeFontCurly',
|
||||||
|
'handleChangeFontPixel',
|
||||||
|
'handleChangeFontChinese',
|
||||||
|
'handleChangeFontJapanese',
|
||||||
|
'handleChangeFontKorean',
|
||||||
|
'handleOpenDropdown',
|
||||||
|
'handleClickOutsideDropdown',
|
||||||
|
'setDropdown',
|
||||||
|
'handleChoose'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
getFontStyle (font) {
|
||||||
|
switch (font) {
|
||||||
|
case Fonts.SERIF:
|
||||||
|
return styles.serif;
|
||||||
|
case Fonts.SANS_SERIF:
|
||||||
|
return styles.sansSerif;
|
||||||
|
case Fonts.HANDWRITING:
|
||||||
|
return styles.handwriting;
|
||||||
|
case Fonts.MARKER:
|
||||||
|
return styles.marker;
|
||||||
|
case Fonts.CURLY:
|
||||||
|
return styles.curly;
|
||||||
|
case Fonts.PIXEL:
|
||||||
|
return styles.pixel;
|
||||||
|
case Fonts.CHINESE:
|
||||||
|
return styles.chinese;
|
||||||
|
case Fonts.JAPANESE:
|
||||||
|
return styles.japanese;
|
||||||
|
case Fonts.KOREAN:
|
||||||
|
return styles.korean;
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getTranslatedFontName (font) {
|
||||||
|
switch (font) {
|
||||||
|
case Fonts.SERIF:
|
||||||
|
return this.props.intl.formatMessage(messages.serif);
|
||||||
|
case Fonts.SANS_SERIF:
|
||||||
|
return this.props.intl.formatMessage(messages.sansSerif);
|
||||||
|
case Fonts.HANDWRITING:
|
||||||
|
return this.props.intl.formatMessage(messages.handwriting);
|
||||||
|
case Fonts.MARKER:
|
||||||
|
return this.props.intl.formatMessage(messages.marker);
|
||||||
|
case Fonts.CURLY:
|
||||||
|
return this.props.intl.formatMessage(messages.curly);
|
||||||
|
case Fonts.PIXEL:
|
||||||
|
return this.props.intl.formatMessage(messages.pixel);
|
||||||
|
case Fonts.CHINESE:
|
||||||
|
return '中文';
|
||||||
|
case Fonts.KOREAN:
|
||||||
|
return '한국어';
|
||||||
|
case Fonts.JAPANESE:
|
||||||
|
return '日本語';
|
||||||
|
default:
|
||||||
|
return font;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleChangeFontSansSerif () {
|
||||||
|
if (this.dropDown.isOpen()) {
|
||||||
|
this.props.changeFont(Fonts.SANS_SERIF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleChangeFontSerif () {
|
||||||
|
if (this.dropDown.isOpen()) {
|
||||||
|
this.props.changeFont(Fonts.SERIF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleChangeFontHandwriting () {
|
||||||
|
if (this.dropDown.isOpen()) {
|
||||||
|
this.props.changeFont(Fonts.HANDWRITING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleChangeFontMarker () {
|
||||||
|
if (this.dropDown.isOpen()) {
|
||||||
|
this.props.changeFont(Fonts.MARKER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleChangeFontCurly () {
|
||||||
|
if (this.dropDown.isOpen()) {
|
||||||
|
this.props.changeFont(Fonts.CURLY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleChangeFontPixel () {
|
||||||
|
if (this.dropDown.isOpen()) {
|
||||||
|
this.props.changeFont(Fonts.PIXEL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleChangeFontChinese () {
|
||||||
|
if (this.dropDown.isOpen()) {
|
||||||
|
this.props.changeFont(Fonts.CHINESE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleChangeFontJapanese () {
|
||||||
|
if (this.dropDown.isOpen()) {
|
||||||
|
this.props.changeFont(Fonts.JAPANESE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleChangeFontKorean () {
|
||||||
|
if (this.dropDown.isOpen()) {
|
||||||
|
this.props.changeFont(Fonts.KOREAN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleChoose () {
|
||||||
|
if (this.dropDown.isOpen()) {
|
||||||
|
this.dropDown.handleClosePopover();
|
||||||
|
this.props.onUpdateImage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleOpenDropdown () {
|
||||||
|
this.savedFont = this.props.font;
|
||||||
|
this.savedSelection = getSelectedLeafItems();
|
||||||
|
}
|
||||||
|
handleClickOutsideDropdown (e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.dropDown.handleClosePopover();
|
||||||
|
|
||||||
|
// Cancel font change
|
||||||
|
for (const item of this.savedSelection) {
|
||||||
|
if (item instanceof paper.PointText) {
|
||||||
|
item.font = this.savedFont;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.changeFont(this.savedFont);
|
||||||
|
this.savedFont = null;
|
||||||
|
this.savedSelection = null;
|
||||||
|
}
|
||||||
|
setDropdown (element) {
|
||||||
|
this.dropDown = element;
|
||||||
|
}
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<FontDropdownComponent
|
||||||
|
componentRef={this.setDropdown}
|
||||||
|
font={this.props.font}
|
||||||
|
getFontStyle={this.getFontStyle}
|
||||||
|
getTranslatedFontName={this.getTranslatedFontName}
|
||||||
|
onChoose={this.handleChoose}
|
||||||
|
onClickOutsideDropdown={this.handleClickOutsideDropdown}
|
||||||
|
onHoverChinese={this.handleChangeFontChinese}
|
||||||
|
onHoverCurly={this.handleChangeFontCurly}
|
||||||
|
onHoverHandwriting={this.handleChangeFontHandwriting}
|
||||||
|
onHoverJapanese={this.handleChangeFontJapanese}
|
||||||
|
onHoverKorean={this.handleChangeFontKorean}
|
||||||
|
onHoverMarker={this.handleChangeFontMarker}
|
||||||
|
onHoverPixel={this.handleChangeFontPixel}
|
||||||
|
onHoverSansSerif={this.handleChangeFontSansSerif}
|
||||||
|
onHoverSerif={this.handleChangeFontSerif}
|
||||||
|
onOpenDropdown={this.handleOpenDropdown}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ModeToolsComponent.propTypes = {
|
||||||
|
changeFont: PropTypes.func.isRequired,
|
||||||
|
font: PropTypes.string,
|
||||||
|
intl: intlShape.isRequired,
|
||||||
|
onUpdateImage: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
font: state.scratchPaint.font
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
changeFont: font => {
|
||||||
|
dispatch(changeFont(font));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(injectIntl(ModeToolsComponent));
|
|
@ -208,6 +208,7 @@ class ModeTools extends React.Component {
|
||||||
onFlipVertical={this.handleFlipVertical}
|
onFlipVertical={this.handleFlipVertical}
|
||||||
onPasteFromClipboard={this.handlePasteFromClipboard}
|
onPasteFromClipboard={this.handlePasteFromClipboard}
|
||||||
onPointPoints={this.handlePointPoints}
|
onPointPoints={this.handlePointPoints}
|
||||||
|
onUpdateImage={this.props.onUpdateImage}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,11 @@ import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
import bindAll from 'lodash.bindall';
|
import bindAll from 'lodash.bindall';
|
||||||
|
import Fonts from '../lib/fonts';
|
||||||
import Modes from '../lib/modes';
|
import Modes from '../lib/modes';
|
||||||
import {MIXED} from '../helper/style-path';
|
import {MIXED} from '../helper/style-path';
|
||||||
|
|
||||||
|
import {changeFont} from '../reducers/font';
|
||||||
import {changeFillColor, DEFAULT_COLOR} from '../reducers/fill-color';
|
import {changeFillColor, DEFAULT_COLOR} from '../reducers/fill-color';
|
||||||
import {changeStrokeColor} from '../reducers/stroke-color';
|
import {changeStrokeColor} from '../reducers/stroke-color';
|
||||||
import {changeMode} from '../reducers/modes';
|
import {changeMode} from '../reducers/modes';
|
||||||
|
@ -42,6 +44,9 @@ class TextMode extends React.Component {
|
||||||
if (this.tool && !nextProps.viewBounds.equals(this.props.viewBounds)) {
|
if (this.tool && !nextProps.viewBounds.equals(this.props.viewBounds)) {
|
||||||
this.tool.onViewBoundsChanged(nextProps.viewBounds);
|
this.tool.onViewBoundsChanged(nextProps.viewBounds);
|
||||||
}
|
}
|
||||||
|
if (this.tool && nextProps.font !== this.props.font) {
|
||||||
|
this.tool.setFont(nextProps.font);
|
||||||
|
}
|
||||||
|
|
||||||
if (nextProps.isTextModeActive && !this.props.isTextModeActive) {
|
if (nextProps.isTextModeActive && !this.props.isTextModeActive) {
|
||||||
this.activateTool();
|
this.activateTool();
|
||||||
|
@ -54,6 +59,7 @@ class TextMode extends React.Component {
|
||||||
}
|
}
|
||||||
activateTool () {
|
activateTool () {
|
||||||
clearSelection(this.props.clearSelectedItems);
|
clearSelection(this.props.clearSelectedItems);
|
||||||
|
|
||||||
// If fill and stroke color are both mixed/transparent/absent, set fill to default and stroke to transparent.
|
// If fill and stroke color are both mixed/transparent/absent, set fill to default and stroke to transparent.
|
||||||
// If exactly one of fill or stroke color is set, set the other one to transparent.
|
// If exactly one of fill or stroke color is set, set the other one to transparent.
|
||||||
// This way the tool won't draw an invisible state, or be unclear about what will be drawn.
|
// This way the tool won't draw an invisible state, or be unclear about what will be drawn.
|
||||||
|
@ -69,14 +75,21 @@ class TextMode extends React.Component {
|
||||||
} else if (fillColorPresent && !strokeColorPresent) {
|
} else if (fillColorPresent && !strokeColorPresent) {
|
||||||
this.props.onChangeStrokeColor(null);
|
this.props.onChangeStrokeColor(null);
|
||||||
}
|
}
|
||||||
|
if (!this.props.font || Object.keys(Fonts).map(key => Fonts[key])
|
||||||
|
.indexOf(this.props.font) < 0) {
|
||||||
|
this.props.changeFont(Fonts.SANS_SERIF);
|
||||||
|
}
|
||||||
|
|
||||||
this.tool = new TextTool(
|
this.tool = new TextTool(
|
||||||
this.props.textArea,
|
this.props.textArea,
|
||||||
this.props.setSelectedItems,
|
this.props.setSelectedItems,
|
||||||
this.props.clearSelectedItems,
|
this.props.clearSelectedItems,
|
||||||
this.props.onUpdateImage,
|
this.props.onUpdateImage,
|
||||||
this.props.setTextEditTarget,
|
this.props.setTextEditTarget,
|
||||||
|
this.props.changeFont
|
||||||
);
|
);
|
||||||
this.tool.setColorState(this.props.colorState);
|
this.tool.setColorState(this.props.colorState);
|
||||||
|
this.tool.setFont(this.props.font);
|
||||||
this.tool.activate();
|
this.tool.activate();
|
||||||
}
|
}
|
||||||
deactivateTool () {
|
deactivateTool () {
|
||||||
|
@ -95,12 +108,14 @@ class TextMode extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
TextMode.propTypes = {
|
TextMode.propTypes = {
|
||||||
|
changeFont: PropTypes.func.isRequired,
|
||||||
clearSelectedItems: PropTypes.func.isRequired,
|
clearSelectedItems: PropTypes.func.isRequired,
|
||||||
colorState: PropTypes.shape({
|
colorState: PropTypes.shape({
|
||||||
fillColor: PropTypes.string,
|
fillColor: PropTypes.string,
|
||||||
strokeColor: PropTypes.string,
|
strokeColor: PropTypes.string,
|
||||||
strokeWidth: PropTypes.number
|
strokeWidth: PropTypes.number
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
|
font: PropTypes.string,
|
||||||
handleMouseDown: PropTypes.func.isRequired,
|
handleMouseDown: PropTypes.func.isRequired,
|
||||||
isTextModeActive: PropTypes.bool.isRequired,
|
isTextModeActive: PropTypes.bool.isRequired,
|
||||||
onChangeFillColor: PropTypes.func.isRequired,
|
onChangeFillColor: PropTypes.func.isRequired,
|
||||||
|
@ -116,12 +131,16 @@ TextMode.propTypes = {
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
colorState: state.scratchPaint.color,
|
colorState: state.scratchPaint.color,
|
||||||
|
font: state.scratchPaint.font,
|
||||||
isTextModeActive: state.scratchPaint.mode === Modes.TEXT,
|
isTextModeActive: state.scratchPaint.mode === Modes.TEXT,
|
||||||
selectedItems: state.scratchPaint.selectedItems,
|
selectedItems: state.scratchPaint.selectedItems,
|
||||||
textEditTarget: state.scratchPaint.textEditTarget,
|
textEditTarget: state.scratchPaint.textEditTarget,
|
||||||
viewBounds: state.scratchPaint.viewBounds
|
viewBounds: state.scratchPaint.viewBounds
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
changeFont: font => {
|
||||||
|
dispatch(changeFont(font));
|
||||||
|
},
|
||||||
clearSelectedItems: () => {
|
clearSelectedItems: () => {
|
||||||
dispatch(clearSelectedItems());
|
dispatch(clearSelectedItems());
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import paper from '@scratch/paper';
|
import paper from '@scratch/paper';
|
||||||
import Modes from '../../lib/modes';
|
import Modes from '../../lib/modes';
|
||||||
import {clearSelection} from '../selection';
|
import {clearSelection, getSelectedLeafItems} from '../selection';
|
||||||
import BoundingBoxTool from '../selection-tools/bounding-box-tool';
|
import BoundingBoxTool from '../selection-tools/bounding-box-tool';
|
||||||
import NudgeTool from '../selection-tools/nudge-tool';
|
import NudgeTool from '../selection-tools/nudge-tool';
|
||||||
import {hoverBounds} from '../guides';
|
import {hoverBounds} from '../guides';
|
||||||
|
@ -36,14 +36,16 @@ class TextTool extends paper.Tool {
|
||||||
* @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} onUpdateImage 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
|
||||||
|
* @param {!function} changeFont Call to change the font in the dropdown
|
||||||
*/
|
*/
|
||||||
constructor (textAreaElement, setSelectedItems, clearSelectedItems, onUpdateImage, setTextEditTarget) {
|
constructor (textAreaElement, setSelectedItems, clearSelectedItems, onUpdateImage, setTextEditTarget, changeFont) {
|
||||||
super();
|
super();
|
||||||
this.element = textAreaElement;
|
this.element = textAreaElement;
|
||||||
this.setSelectedItems = setSelectedItems;
|
this.setSelectedItems = setSelectedItems;
|
||||||
this.clearSelectedItems = clearSelectedItems;
|
this.clearSelectedItems = clearSelectedItems;
|
||||||
this.onUpdateImage = onUpdateImage;
|
this.onUpdateImage = onUpdateImage;
|
||||||
this.setTextEditTarget = setTextEditTarget;
|
this.setTextEditTarget = setTextEditTarget;
|
||||||
|
this.changeFont = changeFont;
|
||||||
this.boundingBoxTool = new BoundingBoxTool(Modes.TEXT, setSelectedItems, clearSelectedItems, onUpdateImage);
|
this.boundingBoxTool = new BoundingBoxTool(Modes.TEXT, setSelectedItems, clearSelectedItems, onUpdateImage);
|
||||||
this.nudgeTool = new NudgeTool(this.boundingBoxTool, onUpdateImage);
|
this.nudgeTool = new NudgeTool(this.boundingBoxTool, onUpdateImage);
|
||||||
this.lastEvent = null;
|
this.lastEvent = null;
|
||||||
|
@ -99,6 +101,20 @@ class TextTool extends paper.Tool {
|
||||||
onSelectionChanged (selectedItems) {
|
onSelectionChanged (selectedItems) {
|
||||||
this.boundingBoxTool.onSelectionChanged(selectedItems);
|
this.boundingBoxTool.onSelectionChanged(selectedItems);
|
||||||
}
|
}
|
||||||
|
setFont (font) {
|
||||||
|
this.font = font;
|
||||||
|
if (this.textBox) {
|
||||||
|
this.textBox.font = font;
|
||||||
|
}
|
||||||
|
const selected = getSelectedLeafItems();
|
||||||
|
for (const item of selected) {
|
||||||
|
if (item instanceof paper.PointText) {
|
||||||
|
item.font = font;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.element.style.fontFamily = font;
|
||||||
|
this.setSelectedItems();
|
||||||
|
}
|
||||||
// Allow other tools to cancel text edit mode
|
// Allow other tools to cancel text edit mode
|
||||||
onTextEditCancelled () {
|
onTextEditCancelled () {
|
||||||
this.endTextEdit();
|
this.endTextEdit();
|
||||||
|
@ -193,7 +209,7 @@ class TextTool extends paper.Tool {
|
||||||
this.textBox = new paper.PointText({
|
this.textBox = new paper.PointText({
|
||||||
point: event.point,
|
point: event.point,
|
||||||
content: '',
|
content: '',
|
||||||
font: 'Helvetica',
|
font: this.font,
|
||||||
fontSize: 30,
|
fontSize: 30,
|
||||||
fillColor: this.colorState.fillColor,
|
fillColor: this.colorState.fillColor,
|
||||||
// Default leading for both the HTML text area and paper.PointText
|
// Default leading for both the HTML text area and paper.PointText
|
||||||
|
@ -272,6 +288,11 @@ class TextTool extends paper.Tool {
|
||||||
beginTextEdit (initialText, matrix) {
|
beginTextEdit (initialText, matrix) {
|
||||||
this.mode = TextTool.TEXT_EDIT_MODE;
|
this.mode = TextTool.TEXT_EDIT_MODE;
|
||||||
this.setTextEditTarget(this.textBox.id);
|
this.setTextEditTarget(this.textBox.id);
|
||||||
|
if (this.font !== this.textBox.font) {
|
||||||
|
this.changeFont(this.textBox.font);
|
||||||
|
}
|
||||||
|
this.element.style.fontSize = `${this.textBox.fontSize}px`;
|
||||||
|
this.element.style.lineHeight = this.textBox.leading / this.textBox.fontSize;
|
||||||
|
|
||||||
const viewMtx = paper.view.matrix;
|
const viewMtx = paper.view.matrix;
|
||||||
|
|
||||||
|
|
13
src/lib/fonts.js
Normal file
13
src/lib/fonts.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
const Fonts = {
|
||||||
|
SANS_SERIF: 'Sans Serif',
|
||||||
|
SERIF: 'Serif',
|
||||||
|
HANDWRITING: 'Handwriting',
|
||||||
|
MARKER: 'Marker',
|
||||||
|
CURLY: 'Curly',
|
||||||
|
PIXEL: 'Pixel',
|
||||||
|
CHINESE: '"Microsoft YaHei", "微软雅黑", STXihei, "华文细黑"',
|
||||||
|
JAPANESE: '"ヒラギノ角ゴ Pro W3", "Hiragino Kaku Gothic Pro", Osaka, "メイリオ", Meiryo, "MS Pゴシック", "MS PGothic"',
|
||||||
|
KOREAN: 'Malgun Gothic'
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Fonts;
|
28
src/reducers/font.js
Normal file
28
src/reducers/font.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import Fonts from '../lib/fonts';
|
||||||
|
|
||||||
|
const CHANGE_FONT = 'scratch-paint/fonts/CHANGE_FONT';
|
||||||
|
const initialState = Fonts.SANS_SERIF;
|
||||||
|
|
||||||
|
const reducer = function (state, action) {
|
||||||
|
if (typeof state === 'undefined') state = initialState;
|
||||||
|
switch (action.type) {
|
||||||
|
case CHANGE_FONT:
|
||||||
|
if (!action.font) return state;
|
||||||
|
return action.font;
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Action creators ==================================
|
||||||
|
const changeFont = function (font) {
|
||||||
|
return {
|
||||||
|
type: CHANGE_FONT,
|
||||||
|
font: font
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
reducer as default,
|
||||||
|
changeFont
|
||||||
|
};
|
|
@ -5,6 +5,7 @@ import brushModeReducer from './brush-mode';
|
||||||
import eraserModeReducer from './eraser-mode';
|
import eraserModeReducer from './eraser-mode';
|
||||||
import colorReducer from './color';
|
import colorReducer from './color';
|
||||||
import clipboardReducer from './clipboard';
|
import clipboardReducer from './clipboard';
|
||||||
|
import fontReducer from './font';
|
||||||
import formatReducer from './format';
|
import formatReducer from './format';
|
||||||
import hoverReducer from './hover';
|
import hoverReducer from './hover';
|
||||||
import modalsReducer from './modals';
|
import modalsReducer from './modals';
|
||||||
|
@ -20,6 +21,7 @@ export default combineReducers({
|
||||||
color: colorReducer,
|
color: colorReducer,
|
||||||
clipboard: clipboardReducer,
|
clipboard: clipboardReducer,
|
||||||
eraserMode: eraserModeReducer,
|
eraserMode: eraserModeReducer,
|
||||||
|
font: fontReducer,
|
||||||
format: formatReducer,
|
format: formatReducer,
|
||||||
hoveredItemId: hoverReducer,
|
hoveredItemId: hoverReducer,
|
||||||
modals: modalsReducer,
|
modals: modalsReducer,
|
||||||
|
|
Loading…
Reference in a new issue