diff --git a/img/contextmenu/btnFont.png b/img/contextmenu/btnFont.png new file mode 100644 index 0000000..6cba1ab Binary files /dev/null and b/img/contextmenu/btnFont.png differ diff --git a/package-lock.json b/package-lock.json index 11e8644..950584e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1932,6 +1932,11 @@ "isarray": "1.0.0" } }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" + }, "buffer-from": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-0.1.1.tgz", @@ -4286,6 +4291,14 @@ } } }, + "fd-slicer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", + "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", + "requires": { + "pend": "1.2.0" + } + }, "figures": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", @@ -5666,6 +5679,33 @@ "pinkie-promise": "2.0.1" } }, + "google-fonts-webpack-plugin": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/google-fonts-webpack-plugin/-/google-fonts-webpack-plugin-0.4.4.tgz", + "integrity": "sha512-+e2D9/DVBG9EDydRovzoqMZ658SsTBGbC0c65GyZqkwNvdj8vRSYQKXqbz7/yt7QaXsCPT1MpH45r3ivWOitcw==", + "requires": { + "lodash": "4.17.4", + "node-fetch": "1.7.3", + "webpack-sources": "0.2.3", + "yauzl": "2.9.1" + }, + "dependencies": { + "source-list-map": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-1.1.2.tgz", + "integrity": "sha1-mIkBnRAkzOVc3AaUmDN+9hhqEaE=" + }, + "webpack-sources": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-0.2.3.tgz", + "integrity": "sha1-F8Yr+vE8cH+dAsR54Nzd6DgGl/s=", + "requires": { + "source-list-map": "1.1.2", + "source-map": "0.5.7" + } + } + } + }, "got": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/got/-/got-3.3.1.tgz", @@ -10550,6 +10590,11 @@ "integrity": "sha1-z4uvrm7d/0tafvsYUmnqr0YQ3b0=", "dev": true }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" + }, "pepjs": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/pepjs/-/pepjs-0.4.3.tgz", @@ -14364,6 +14409,15 @@ } } }, + "yauzl": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.9.1.tgz", + "integrity": "sha1-qBmB6nCleUYTOIPwKcWCGok1mn8=", + "requires": { + "buffer-crc32": "0.2.13", + "fd-slicer": "1.0.1" + } + }, "yml-loader": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/yml-loader/-/yml-loader-2.1.0.tgz", diff --git a/package.json b/package.json index 6a2f517..c3076bd 100755 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "blueimp-canvas-to-blob": "^3.14.0", "bowser": "^1.8.1", "fit-curve": "^0.1.6", + "google-fonts-webpack-plugin": "^0.4.4", "imports-loader": "^0.7.1", "jss": "^9.4.0", "keycode": "^2.1.9", diff --git a/src/actions/index.js b/src/actions/index.js index d2eceab..64285fe 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -390,8 +390,8 @@ export function d2textInit(position, textId, screenMatrixContainer, screenMatrix dispatch({ type: D2_TEXT_INIT, position, textId, screenMatrixContainer, screenMatrixZoom }); }; } -export function d2textInputChange(text, family, weight, style, fill) { - return { type: D2_TEXT_INPUT_CHANGE, text, family, weight, style, fill }; +export function d2textInputChange(text) { + return { type: D2_TEXT_INPUT_CHANGE, text }; } const traceDragThrottle = createThrottle(); diff --git a/src/components/InputText.js b/src/components/InputText.js index 9b31ab2..fe8528b 100644 --- a/src/components/InputText.js +++ b/src/components/InputText.js @@ -37,10 +37,9 @@ class InputText extends React.Component { if (!shapeData) return; const { changeText } = this.props; - const { family, weight, style } = shapeData.text; const text = this.refs.text.value; - changeText(text, family, weight, style, true); + changeText(text); }; getShapeData = () => { @@ -71,7 +70,7 @@ class InputText extends React.Component { > 0) { component = ( ); + } else if (FONT_TOOLS.includes(child.value)) { + component = ( + +

{FONT_FACE[child.value]}

+
+ ); } else { component = ( = 2; const showIntersect = activeTool === d2Tools.TRANSFORM && numSelectedObjects >= 1; return { @@ -191,11 +206,14 @@ function filterMenus(activeTool, numSelectedObjects, numFilledObjects, numSolidO case contextTools.HOLE_TOGGLE: return numSelectedObjects > 0; + case contextTools.FONT: + return selectionIncludesText || activeTool === d2Tools.TEXT; + default: return true; } }).map(child => { - return filterMenus(activeTool, numSelectedObjects, numFilledObjects, numSolidObjects, child); + return filterMenus(activeTool, numSelectedObjects, numFilledObjects, numSolidObjects, selectionIncludesText, child); }) }; } @@ -217,11 +235,12 @@ const getMenus = createSelector([ state => state.sketcher.present.d2.tool, state => state.sketcher.present.selection.objects.length, state => state.sketcher.present.selection.objects.filter(({ id }) => state.sketcher.present.objectsById[id].fill).length, - state => state.sketcher.present.selection.objects.filter(({ id }) => state.sketcher.present.objectsById[id].solid).length -], (menus, activeTool, numSelectedObjects, numFilledObjects, numSolidObjects) => ({ - toolbar2d: filterMenus(activeTool, numSelectedObjects, numFilledObjects, numSolidObjects, nestChildren(menus, menus[TOOLBAR2D])), - toolbar3d: filterMenus(activeTool, numSelectedObjects, numFilledObjects, numSolidObjects, nestChildren(menus, menus[TOOLBAR3D])), - context: filterMenus(activeTool, numSelectedObjects, numFilledObjects, numSolidObjects, nestChildren(menus, menus[CONTEXT])) + state => state.sketcher.present.selection.objects.filter(({ id }) => state.sketcher.present.objectsById[id].solid).length, + state => state.sketcher.present.selection.objects.some(({ id }) => state.sketcher.present.objectsById[id].type === 'TEXT') +], (menus, activeTool, numSelectedObjects, numFilledObjects, numSolidObjects, selectionIncludesText) => ({ + toolbar2d: filterMenus(activeTool, numSelectedObjects, numFilledObjects, numSolidObjects, selectionIncludesText, nestChildren(menus, menus[TOOLBAR2D])), + toolbar3d: filterMenus(activeTool, numSelectedObjects, numFilledObjects, numSolidObjects, selectionIncludesText, nestChildren(menus, menus[TOOLBAR3D])), + context: filterMenus(activeTool, numSelectedObjects, numFilledObjects, numSolidObjects, selectionIncludesText, nestChildren(menus, menus[CONTEXT])) })); export default injectSheet(styles)(connect(getMenus)(SketcherToolbars)); diff --git a/src/constants/contextTools.js b/src/constants/contextTools.js index 382b2ea..aea829b 100644 --- a/src/constants/contextTools.js +++ b/src/constants/contextTools.js @@ -8,6 +8,7 @@ export const HOLE_TOGGLE = 'hole-toggle-tool'; export const ALIGN = 'align-tool'; export const UNION = 'union-tool'; export const INTERSECT = 'intersect-tool'; +export const FONT = 'font-tool'; export const LIGHT_BLUE_A = 'color-light-blue-a'; export const LIGHT_BLUE_B = 'color-light-blue-b'; @@ -79,3 +80,23 @@ export const ALIGN_TOOLS = [ ALIGN_VERTICAL, ALIGN_BOTTOM ]; + +export const OSWALD = 'oswald'; +export const RANGA = 'ranga'; +export const JOTI_ONE = 'joti-one'; +export const BELLEFAIR = 'bellefair'; +export const LOBSTER = 'lobster'; +export const ABRIL_FATFACE = 'abril-fatface'; +export const PLAY = 'play'; +export const FASCINATE = 'fascinate'; + +export const FONT_TOOLS = [ + OSWALD, + RANGA, + JOTI_ONE, + BELLEFAIR, + LOBSTER, + ABRIL_FATFACE, + PLAY, + FASCINATE +]; diff --git a/src/constants/general.js b/src/constants/general.js index ebb8606..4f84533 100644 --- a/src/constants/general.js +++ b/src/constants/general.js @@ -37,3 +37,14 @@ export const COLOR_STRING_TO_HEX = { [contextTools.BLACK_B]: 0xAAAAAA, [contextTools.BLACK_C]: 0x444444 }; + +export const FONT_FACE = { + [contextTools.OSWALD]: 'Oswald', + [contextTools.RANGA]: 'Ranga', + [contextTools.JOTI_ONE]: 'Joti One', + [contextTools.BELLEFAIR]: 'Bellefair', + [contextTools.LOBSTER]: 'Lobster', + [contextTools.ABRIL_FATFACE]: 'Abril Fatface', + [contextTools.PLAY]: 'Play', + [contextTools.FASCINATE]: 'Fascinate' +}; diff --git a/src/constants/menu.js b/src/constants/menu.js index 680bb66..7377723 100644 --- a/src/constants/menu.js +++ b/src/constants/menu.js @@ -109,6 +109,11 @@ const context = { selected: contextTools.ALIGN_HORIZONTAL, children: contextTools.ALIGN_TOOLS.map(value => ({ value })), ...selectorBehavior + }, { + value: contextTools.FONT, + selected: contextTools.OSWALD, + children: contextTools.FONT_TOOLS.map(value => ({ value })), + ...selectorBehavior }, { value: contextTools.UNION }, { value: contextTools.INTERSECT } diff --git a/src/constants/shapeTypeProperties.js b/src/constants/shapeTypeProperties.js index 4e2f0a4..ca5b2f1 100644 --- a/src/constants/shapeTypeProperties.js +++ b/src/constants/shapeTypeProperties.js @@ -1,6 +1,8 @@ import { Vector, Matrix } from '@doodle3d/cal'; import * as d2Tools from './d2Tools'; import * as d3Tools from './d3Tools'; +import * as contextTools from './contextTools'; +import { FONT_FACE } from '../constants/general.js'; const SHAPE = { D3Visible: true, @@ -58,7 +60,7 @@ export const SHAPE_TYPE_PROPERTIES = { ...SHAPE, defaultProperties: { ...defaultProperties, - text: { text: '', family: 'Arial', weight: 'normal', style: 'normal' }, + text: { text: '', family: FONT_FACE[contextTools.OSWALD], weight: 'normal', style: 'normal' }, fill: true } }, diff --git a/src/reducer/contextReducer.js b/src/reducer/contextReducer.js index 34418b9..087ea76 100644 --- a/src/reducer/contextReducer.js +++ b/src/reducer/contextReducer.js @@ -1,6 +1,6 @@ import update from 'react-addons-update'; import * as contextTools from '../constants/contextTools.js'; -import { COLOR_STRING_TO_HEX } from '../constants/general.js'; +import { COLOR_STRING_TO_HEX, FONT_FACE } from '../constants/general.js'; import { ERASER_SIZES, BRUSH_SIZES } from '../constants/d2Constants.js'; import { SHAPE_TYPE_PROPERTIES } from '../constants/shapeTypeProperties.js'; import * as actions from '../actions/index.js'; @@ -69,6 +69,33 @@ export default function (state, action) { }); } + case contextTools.OSWALD: + case contextTools.RANGA: + case contextTools.JOTI_ONE: + case contextTools.BELLEFAIR: + case contextTools.LOBSTER: + case contextTools.ABRIL_FATFACE: + case contextTools.PLAY: + case contextTools.FASCINATE: { + const family = FONT_FACE[action.tool]; + const { activeShape } = state.d2; + if (activeShape && state.objectsById[activeShape].type === 'TEXT') { + state = update(state, { objectsById: { [activeShape]: { text: { family: { $set: family } } } } }); + } + + return update(state, { + objectsById: state.selection.objects.reduce((updateObject, { id }) => { + if (state.objectsById[id].type === 'TEXT') { + updateObject[id] = { text: { family: { $set: FONT_FACE[action.tool] } } }; + } + return updateObject; + }, {}), + context: { + font: { $set: FONT_FACE[action.tool] } + } + }); + } + case contextTools.LIGHT_BLUE_A: case contextTools.LIGHT_BLUE_B: case contextTools.LIGHT_BLUE_C: @@ -94,6 +121,11 @@ export default function (state, action) { case contextTools.BLACK_B: case contextTools.BLACK_C: { const color = COLOR_STRING_TO_HEX[action.tool]; + const { activeShape } = state.d2; + if (activeShape) { + state = update(state, { objectsById: { [activeShape]: { color: { $set: color } } } }); + } + return updateColor(state, color); } diff --git a/src/reducer/d2/tools/textReducer.js b/src/reducer/d2/tools/textReducer.js index 077c3ac..30fef0a 100644 --- a/src/reducer/d2/tools/textReducer.js +++ b/src/reducer/d2/tools/textReducer.js @@ -8,7 +8,6 @@ const debug = createDebug('d3d:reducer:text'); export default function textReducer(state, action) { if (action.log !== false) debug(action.type); - switch (action.type) { case actions.D2_TEXT_INIT: { state = removeEmptyText(state); @@ -23,24 +22,26 @@ export default function textReducer(state, action) { } else { return addObjectActive2D(state, { transform: new Matrix({ x: screenPosition.x, y: screenPosition.y }), - type: 'TEXT' + type: 'TEXT', + text: { + text: '', + family: state.context.font, + weight: 'normal', + style: 'normal' + } }); } return state; } case actions.D2_TEXT_INPUT_CHANGE: { - const { text, family, style, weight, fill } = action; + const { text } = action; const { activeShape } = state.d2; return update(state, { objectsById: { [activeShape]: { text: { - text: { $set: text }, - family: { $set: family }, - style: { $set: style }, - weight: { $set: weight } - }, - fill: { $set: fill } + text: { $set: text } + } } } }); diff --git a/src/reducer/index.js b/src/reducer/index.js index 141a060..3330090 100644 --- a/src/reducer/index.js +++ b/src/reducer/index.js @@ -4,7 +4,7 @@ import undoFilter from '../utils/undoFilter.js'; import * as actions from '../actions/index.js'; import * as d2Tools from '../constants/d2Tools.js'; import * as d3Tools from '../constants/d3Tools.js'; -import { COLOR_STRING_TO_HEX } from '../constants/general.js'; +import { COLOR_STRING_TO_HEX, FONT_FACE } from '../constants/general.js'; import * as contextTools from '../constants/contextTools.js'; import { ERASER_SIZES, BRUSH_SIZES } from '../constants/d2Constants.js'; import update from 'react-addons-update'; @@ -36,7 +36,8 @@ const initialState = { objectIdCounter: 0, context: { solid: true, - color: COLOR_STRING_TO_HEX[contextTools.LIGHT_BLUE_B] + color: COLOR_STRING_TO_HEX[contextTools.LIGHT_BLUE_B], + font: FONT_FACE[contextTools.OSWALD] }, selection: { transform: new Matrix(), diff --git a/styles/styles.css b/styles/styles.css index 656703f..c937f2b 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -249,6 +249,13 @@ flex-wrap: wrap; } +#font-tool > .button { + background-image: url('../img/contextmenu/btnFont.png'); + background-size: 40px auto; + width: 40px; + height: 40px; +} + #color-light-blue-a { fill: #BCFFFF; } #color-light-blue-b { fill: #68E1FD; } #color-light-blue-c { fill: #01B8FF; } diff --git a/webpack.config.js b/webpack.config.js index c116e80..22b7fc5 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -3,6 +3,7 @@ const path = require('path'); const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const HTMLWebpackPlugin = require('html-webpack-plugin'); const CordovaPlugin = require('webpack-cordova-plugin'); +const GoogleFontsPlugin = require('google-fonts-webpack-plugin'); const devMode = process.env.NODE_ENV !== 'production'; const appMode = process.env.TARGET === 'app'; @@ -75,9 +76,7 @@ module.exports = { }, plugins: [ new webpack.DefinePlugin({ - 'process.env': { - 'TARGET': JSON.stringify(process.env.TARGET) - } + 'process.env.TARGET': JSON.stringify(process.env.TARGET) }), new HTMLWebpackPlugin({ title: 'Doodle3D Core - Simple example', @@ -86,6 +85,18 @@ module.exports = { scripts: appMode ? ['cordova.js'] : null, appMountId: 'app' }), + new GoogleFontsPlugin({ + fonts: [ + { family: 'Oswald' }, + { family: 'Ranga' }, + { family: 'Joti One' }, + { family: 'Bellefair' }, + { family: 'Lobster' }, + { family: 'Abril Fatface' }, + { family: 'Play' }, + { family: 'Fascinate' } + ] + }), ...(appMode ? [ new CordovaPlugin({ config: 'config.xml', @@ -95,7 +106,7 @@ module.exports = { }) ] : []) ], - devtool: "source-map", + devtool: 'source-map', devServer: { contentBase: 'dist' }