From 20995d0ac7ef0dec6cee47cf141ba70503a0be3b Mon Sep 17 00:00:00 2001 From: casperlamboo Date: Tue, 14 Nov 2017 16:01:04 +0100 Subject: [PATCH] fix imports --- src/reducer/contextReducer.js | 177 ++++++++++++++++++ src/reducer/d2/tools/bucketReducer.js | 42 ++--- src/reducer/d3/tools/heightReducer.js | 4 +- src/reducer/d3/tools/stampReducer.js | 6 +- src/reducer/menusReducer.js | 97 ++++++++++ .../{objectReducer.js => objectReducers.js} | 0 src/reducer/pointsReducers.js | 136 ++++++++++++++ src/reducer/selectionReducer.js | 2 +- src/utils/objectSelectors.js | 67 +++++++ 9 files changed, 502 insertions(+), 29 deletions(-) create mode 100644 src/reducer/contextReducer.js create mode 100644 src/reducer/menusReducer.js rename src/reducer/{objectReducer.js => objectReducers.js} (100%) create mode 100644 src/reducer/pointsReducers.js create mode 100644 src/utils/objectSelectors.js diff --git a/src/reducer/contextReducer.js b/src/reducer/contextReducer.js new file mode 100644 index 0000000..6928184 --- /dev/null +++ b/src/reducer/contextReducer.js @@ -0,0 +1,177 @@ +import update from 'react-addons-update'; +import * as contextTools from 'src/js/constants/contextTools.js'; +import { COLOR_STRING_TO_HEX, COLOR_HEX_TO_STRING } from 'src/js/constants/general.js'; +import { ERASER_SIZES, BRUSH_SIZES } from 'src/js/constants/d2Constants.js'; +import * as actions from 'src/js/actions/index.js'; +import { select } from './menusReducer.js'; +import { getSelectedObjectsSelector, getBoundingBox } from 'src/js/utils/selectionUtils.js'; +import { Matrix } from 'cal'; + +export default function (state, action) { + switch (action.category) { + case actions.sketcher.CAT_SELECTION: { + let menus = state.menus; + + const [firstSelected] = state.selection.objects; + const colorHex = firstSelected ? state.objectsById[firstSelected.id].color : state.context.color; + // pick current draw color when color is unknown + const color = COLOR_HEX_TO_STRING[colorHex] || COLOR_HEX_TO_STRING[state.context.color]; + menus = select(menus, color); + + const fillBool = firstSelected && state.objectsById[firstSelected.id].fill; + const fill = fillBool ? contextTools.FILL_TOGGLE_FILL : contextTools.FILL_TOGGLE_OUTLINE; + menus = select(menus, fill); + + return update(state, { menus: { $set: menus } }); + } + + default: + break; + } + + switch (action.type) { + case actions.sketcher.D2_CHANGE_TOOL: { + const color = COLOR_HEX_TO_STRING[state.context.color]; + return update(state, { + menus: { $set: select(state.menus, color) } + }); + } + + default: + break; + } + + switch (action.tool) { + case contextTools.LIGHT_BLUE: + case contextTools.LIGHT_GREEN: + case contextTools.LIGHT_PINK: + case contextTools.LIGHT_YELLOW: + case contextTools.BLUE: + case contextTools.GREEN: + case contextTools.PINK: + case contextTools.YELLOW: + case contextTools.DARK_BLUE: + case contextTools.DARK_GREEN: + case contextTools.DARK_PINK: + case contextTools.DARK_YELLOW: { + const color = COLOR_STRING_TO_HEX[action.tool]; + return update(state, { + objectsById: state.selection.objects.reduce((updateObject, { id }) => { + updateObject[id] = { color: { $set: color } }; + return updateObject; + }, {}), + context: { + color: { $set: color } + } + }); + } + + case contextTools.ERASER_SIZE_SMALL: + case contextTools.ERASER_SIZE_MEDIUM: + case contextTools.ERASER_SIZE_LARGE: { + const size = ERASER_SIZES[action.tool]; + return update(state, { + d2: { + eraser: { + size: { $set: size } + } + } + }); + } + + case contextTools.BRUSH_SIZE_SMALL: + case contextTools.BRUSH_SIZE_MEDIUM: + case contextTools.BRUSH_SIZE_LARGE: { + const size = BRUSH_SIZES[action.tool]; + return update(state, { + d2: { + brush: { + size: { $set: size } + } + } + }); + } + + case contextTools.FILL_TOGGLE_FILL: + case contextTools.FILL_TOGGLE_OUTLINE: { + const fill = action.tool === contextTools.FILL_TOGGLE_FILL; + + return update(state, { + objectsById: state.selection.objects.reduce((updateObject, { id }) => { + updateObject[id] = { fill: { $set: fill } }; + return updateObject; + }, {}) + }); + } + + case contextTools.ALIGN_LEFT: + case contextTools.ALIGN_HORIZONTAL: + case contextTools.ALIGN_RIGHT: + case contextTools.ALIGN_TOP: + case contextTools.ALIGN_VERTICAL: + case contextTools.ALIGN_BOTTOM: { + if (state.selection.objects < 2) return state; + + const selection = state.selection; + const selectedShapeDatas = getSelectedObjectsSelector(selection.objects, state.objectsById); + const totalBoundingBox = getBoundingBox(selectedShapeDatas); + + for (const shapeData of selectedShapeDatas) { + const boundingBox = getBoundingBox([shapeData]); + + let deltaX = 0; + let deltaY = 0; + switch (action.tool) { + case contextTools.ALIGN_LEFT: + deltaX = totalBoundingBox.min.x - boundingBox.min.x; + break; + case contextTools.ALIGN_HORIZONTAL: + deltaX = totalBoundingBox.center.x - boundingBox.center.x; + break; + case contextTools.ALIGN_RIGHT: + deltaX = totalBoundingBox.max.x - boundingBox.max.x; + break; + case contextTools.ALIGN_TOP: + deltaY = totalBoundingBox.min.z - boundingBox.min.z; + break; + case contextTools.ALIGN_VERTICAL: + deltaY = totalBoundingBox.center.y - boundingBox.center.y; + break; + case contextTools.ALIGN_BOTTOM: + deltaY = totalBoundingBox.max.z - boundingBox.max.z; + break; + default: + break; + } + + const transform = state.objectsById[shapeData.UID].transform.translate(deltaX, deltaY); + + state = update(state, { + objectsById: { + [shapeData.UID]: { + transform: { $set: transform } + } + }, + selection: { + objects: { + [selection.objects.findIndex(({ id }) => id === shapeData.UID)]: { + initialTransform: { $set: transform } + } + } + } + }); + } + + state = update(state, { + selection: { + transform: { $set: new Matrix() } + } + }); + + return state; + } + + default: + return state; + } +} diff --git a/src/reducer/d2/tools/bucketReducer.js b/src/reducer/d2/tools/bucketReducer.js index bc62281..ebf155e 100644 --- a/src/reducer/d2/tools/bucketReducer.js +++ b/src/reducer/d2/tools/bucketReducer.js @@ -1,16 +1,14 @@ +import update from 'react-addons-update'; +import fillPath from 'fill-path'; +import ClipperShape from 'clipper-js'; import * as actions from '../../../actions/index.js'; import { SHAPE_TYPE_PROPERTIES } from '../../../constants/shapeTypeProperties.js'; import { LINE_WIDTH, CLIPPER_PRECISION } from '../../../constants/d2Constants.js'; -import createDebug from 'debug'; -import { shapeToPoints, applyMatrixOnShape, pathToVectorPath } from '../../../utils/shapeDataUtils.js'; +import { shapeToPoints, applyMatrixOnShape, pathToVectorPath } from '../../../shape/shapeDataUtils.js'; import { addObject } from '../../../reducers/objectReducers.js'; -import fillPath from 'fill-path'; -import ClipperShape from 'clipper-js'; import subtractShapeFromState from '../../../utils/subtractShapeFromState.js'; -import update from 'react-addons-update'; -import { get as getConfig } from '../../../services/config.js'; import { getColor, getFirst, filterType, getObjectsFromIds } from '../../../utils/objectSelectors.js'; - +import createDebug from 'debug'; const debug = createDebug('d3d:reducer:bucket'); const MITER_LIMIT = 30.0; @@ -20,22 +18,20 @@ export default function bucketReducer(state, action) { switch (action.type) { case actions.D2_TAP: - const { experimentalColorPicker } = getConfig(); - - let color = state.context.color; - if (experimentalColorPicker) { - const imageColor = getColor( - getFirst( - filterType( - getObjectsFromIds(state, action.objects), - 'IMAGE_GUIDE' - ) - ), - action.position, - action.screenMatrixZoom - ); - if (imageColor !== null) color = imageColor; - } + const color = state.context.color; + // if (experimentalColorPicker) { + // const imageColor = getColor( + // getFirst( + // filterType( + // getObjectsFromIds(state, action.objects), + // 'IMAGE_GUIDE' + // ) + // ), + // action.position, + // action.screenMatrixZoom + // ); + // if (imageColor !== null) color = imageColor; + // } // if clicked on a filled shape change shape color const filledPathIndex = action.objects.findIndex(id => ( diff --git a/src/reducer/d3/tools/heightReducer.js b/src/reducer/d3/tools/heightReducer.js index 83cd2cb..59681b6 100644 --- a/src/reducer/d3/tools/heightReducer.js +++ b/src/reducer/d3/tools/heightReducer.js @@ -1,8 +1,8 @@ import update from 'react-addons-update'; import { Utils } from 'cal'; import * as THREE from 'three'; -import { SHAPE_TYPE_PROPERTIES } from '../../../constatants/shapeTypeProperties.js'; -import * as d3Tools from '../../../constatants/d3Tools.js'; +import { SHAPE_TYPE_PROPERTIES } from '../../../constants/shapeTypeProperties.js'; +import * as d3Tools from '../../../constants/d3Tools.js'; import { getSelectedObjectsSelector, getBoundingBox } from '../../../utils/selectionUtils.js'; import * as actions from '../../../actions/index.js'; // import createDebug from 'debug'; diff --git a/src/reducer/d3/tools/stampReducer.js b/src/reducer/d3/tools/stampReducer.js index 78a7db1..cdc5d47 100644 --- a/src/reducer/d3/tools/stampReducer.js +++ b/src/reducer/d3/tools/stampReducer.js @@ -1,7 +1,7 @@ -import * as actions from '../../actions/index.js'; +import * as actions from '../../../actions/index.js'; import { addObject, addSpaceActive } from '../../objectReducers.js'; -import { recursiveClone } from '../../utils/clone.js'; -import { getBoundingBox, getSelectedObjectsSelector } from '../../utils/selectionUtils.js'; +import { recursiveClone } from '../../../utils/clone.js'; +import { getBoundingBox, getSelectedObjectsSelector } from '../../../utils/selectionUtils.js'; import { updateInitTransform } from '../../d2/tools/transformReducer.js'; import update from 'react-addons-update'; import * as THREE from 'three'; diff --git a/src/reducer/menusReducer.js b/src/reducer/menusReducer.js new file mode 100644 index 0000000..c055cc7 --- /dev/null +++ b/src/reducer/menusReducer.js @@ -0,0 +1,97 @@ +import * as actions from 'src/js/actions/index.js'; +import * as d2Tools from 'src/js/constants/d2Tools.js'; +import initialMenuStructure from 'src/js/constants/menu.js'; +// import createDebug from 'debug'; +// const debug = createDebug('d3d:reducer:menu:index'); + +// flatten & add menu structure +const initialState = addChildren({}, initialMenuStructure); + +// read children recursivly and add them flat/unnested to the state +function addChildren(state, childrenData) { + return childrenData.reduce((reducedState, childData) => { + reducedState = addItem(reducedState, childData); + if (childData.children) { + reducedState = addChildren(reducedState, childData.children); + } + return reducedState; + }, { ...state }); +} +function addItem(state, data) { + state[data.value] = { + disabled: false, + selected: '', + open: false, + ...data, // override defaults with given data + children: getChildrenValues(data.children) + }; + + return state; +} +function getChildrenValues(childrenData = []) { + return childrenData.map(child => child.value); +} + +// item specific reducer +function item(state, action) { + switch (action.type) { + case actions.sketcher.MENU_OPEN: + case actions.sketcher.MENU_CLOSE: + return { + ...state, + open: (action.type === actions.sketcher.MENU_OPEN) + }; + default: + return state; + } +} + +function getMenu(state, targetValue) { + if (state[targetValue] === undefined) { + throw new Error(`Can't find menu item '${targetValue}'`); + } + for (const value in state) { + if (state[value].children.indexOf(targetValue) !== -1) { + return value; + } + } + return null; +} +export const select = (state, value) => { + const menuValue = getMenu(state, value); + // debug(`selectItem: ${value} in ${menuValue}`); + if (menuValue === null) return state; + // select value in menu + state = { + ...state, + [menuValue]: { + ...state[menuValue], + selected: value + } + }; + // try selecting menu in it's menu + state = select(state, menuValue); + return state; +}; + +export default function menusReducer(state = initialState, action) { + if (action.category === actions.sketcher.CAT_SELECTION) { + state = select(state, d2Tools.TRANSFORM); + } + switch (action.type) { + case actions.sketcher.MENU_OPEN: + case actions.sketcher.MENU_CLOSE: + if (action.menuValue === undefined) return state; + return { + ...state, + [action.menuValue]: item(state[action.menuValue], action) + }; + case actions.sketcher.D2_CHANGE_TOOL: + case actions.sketcher.D3_CHANGE_TOOL: + case actions.sketcher.CONTEXT_CHANGE_TOOL: + // recursivly select items in menu's + return select(state, action.tool); + default: + return state; + } +} diff --git a/src/reducer/objectReducer.js b/src/reducer/objectReducers.js similarity index 100% rename from src/reducer/objectReducer.js rename to src/reducer/objectReducers.js diff --git a/src/reducer/pointsReducers.js b/src/reducer/pointsReducers.js new file mode 100644 index 0000000..a726c2e --- /dev/null +++ b/src/reducer/pointsReducers.js @@ -0,0 +1,136 @@ +import R from 'ramda'; +// import createDebug from 'debug'; +// const debug = createDebug('d3d:reducers:points'); + +export function mergePaths(pathA, pathB, matchIndexes) { + if (matchIndexes[0] !== -1) { // if first path's first point matches + // always reverse first path + const sortedA = R.reverse(pathA); + // if matches with second path's second point reverse + const sortedB = (matchIndexes[0] === 1) ? R.reverse(pathB) : pathB; + // add b behind a, removing overlapping point + return sortedA.concat(sortedB.slice(1)); + } else if (matchIndexes[1] !== -1) { // if first path's second point matches + // no need to reorder first path + const sortedA = pathA; + // if matches with second path's second point reverse + const sortedB = (matchIndexes[1] === 1) ? R.reverse(pathB) : pathB; + // add b behind a, removing overlapping point + return sortedA.concat(sortedB.slice(1)); + } else { + return null; + } +} + +// get just the end points of a path +export const getEndPoints = R.juxt([R.head, R.last]); + +export const getEndPointPaths = R.map(getEndPoints); + +const vectorEquals = R.curry( + (a, b) => a.equals(b) +); + +// find the index of a point in an array of points +export const findPointIndex = R.curry( + (points, point) => R.findIndex(vectorEquals(point), points) +); +export const findEndPointIndex = R.curry( + (points, point) => findPointIndex(getEndPoints(points), point) +); + +// find indexes of multiple points in array of points, +// returned as array of indexes +// Result examples: +// [0]: a's first point matches b's first point +// [1]: a's first point matches b's second point +// [-1]: a's first point matches none of b's points +// [1, 0]: a's first point matches and b's second point and a's second point matches b's first point +export const findPointsIndexes = R.curry( + (pointsA, pointsB) => R.map(findPointIndex(pointsB), pointsA) +); +export const findEndPointsIndexes = R.curry( + (pathA, pathB) => findPointsIndexes( + getEndPoints(pathA), + getEndPoints(pathB) + ) +); + +// find the index of a point in an array of paths +// (paths are arrays of points) +// returns first result +// returns array with +// - index of path with matching point +// - matching index (see findPointsIndexes) +export function findPointIndexInPaths(point, paths) { + // debug('findPointIndexInPaths: ', toString(point), toString(paths)); + for (let i = 0; i < paths.length; i++) { + const path = paths[i]; + // find matching endpoints + const index = findPointIndex(path, point); + // debug(` ${i}: index: `, index); + // if found stop searching + if (index > -1) return [i, index]; + } + return null; +} +export const findEndPointIndexInPaths = R.curry( + (point, paths) => findPointIndexInPaths(point, getEndPointPaths(paths)) +); + +// find indexes of matching points of path inside other path +// returns first result +// returns array with +// - index of path with matching point +// - array of matching indexes (see findPointsIndexes) +export function findPointsIndexesOfPathInPaths(paths, path) { + // console.log('findPointsIndexesOfPathInPaths'); + // console.log(' paths: ', toString(paths)); + // console.log(' path: ', toString(path)); + for (let i = 0; i < paths.length; i++) { + const otherPath = paths[i]; + // skip current path + if (otherPath === path) continue; + // find matching endpoints + const indexes = findPointsIndexes(path, otherPath); + // console.log(` ${i}: indexes: `, indexes); + // found any matching indexes? + // indexes contains any item that's larget than -1 + const foundMatch = R.any(R.lt(-1))(indexes); + // if found stop searching + // return index of matching path and indexes of machting end points + if (foundMatch) return [i, indexes]; + } + return null; +} + +// find indexes of matching end points of path inside other path +// see: findPointsIndexesOfPathInPaths +export function findEndPointsIndexesOfPathInPaths(paths, path) { + return findPointsIndexesOfPathInPaths( + R.map(getEndPoints, paths), + getEndPoints(path) + ); +} + +// cross-compare points of multiple paths +// returns first match +// array with +// - index of path A with matching point +// - index of path B with matching point +// - array of matching indexes (see findPointsIndexes) +export function findPointIndexesOfPaths(paths) { + // console.log('findPointIndexesOfPaths'); + for (let i = 0; i < paths.length; i++) { + const path = paths[i]; + const indexes = findPointsIndexesOfPathInPaths(paths, path); + // console.log(' indexes: ', toString(indexes)); + if (indexes !== null) return [i, ...indexes]; + } + return null; +} + +// cross-compare endpoints of multiple paths +export function findEndPointIndexesOfPaths(paths) { + return findPointIndexesOfPaths(R.map(getEndPoints, paths)); +} diff --git a/src/reducer/selectionReducer.js b/src/reducer/selectionReducer.js index 033c911..f1f3af2 100644 --- a/src/reducer/selectionReducer.js +++ b/src/reducer/selectionReducer.js @@ -1,7 +1,7 @@ import update from 'react-addons-update'; import * as actions from '../actions/index.js'; import { Vector } from 'cal'; -import { shapeToPoints } from '../utils/shapeDataUtils.js'; +import { shapeToPoints } from '../shape/shapeDataUtils.js'; import createDebug from 'debug'; const debug = createDebug('d3d:reducer:selection'); diff --git a/src/utils/objectSelectors.js b/src/utils/objectSelectors.js new file mode 100644 index 0000000..5753de8 --- /dev/null +++ b/src/utils/objectSelectors.js @@ -0,0 +1,67 @@ +import { SHAPE_TYPE_PROPERTIES } from 'src/js/constants/shapeTypeProperties.js'; +import { calculatePointInImage } from 'src/js/utils/matrixUtils.js'; +import R from 'ramda'; +// import createDebug from 'debug'; +// const debug = createDebug('d3d:utils:objectSelectors'); + +// returns true if object is a closed shape +export function closedShapesFilter({ points }) { + return points[0].equals(points[points.length - 1]); +} +// returns true if object is a open (not closed) shape +export function openShapesFilter(object) { + return !closedShapesFilter(object); +} +// returns true if object should snap +export function snappingFilter({ type }) { + return SHAPE_TYPE_PROPERTIES[type].snapping; +} + +export function getObjectsById(state) { + return state.sketcher.present.objectsById; +} + +export function contains(state, testFn) { + const objects = R.values(getObjectsById(state)); + const index = R.findIndex(testFn, objects); + return (index !== -1); +} + +export function containsType(state, type) { + return contains(state, R.propEq('type', type)); +} + +export function getSnappingPoints(state, matrix) { + return Object.values(state.objectsById) + .filter(snappingFilter) // filter closed shapes + .filter(openShapesFilter) + .map(shapeData => { + const transform = matrix ? shapeData.transform.multiplyMatrix(matrix) : shapeData.transform; + const startPoint = shapeData.points[0].applyMatrix(transform); + const endPoint = shapeData.points[shapeData.points.length - 1].applyMatrix(transform); + + return { shapeData, startPoint, endPoint }; + }); +} +export function getObjectsFromIds(state, ids) { + return ids.map(id => state.objectsById[id]); +} +export function filterType(objects, type) { + return objects.filter(value => value.type === type); +} +export function getFirst(objects) { + return objects.length > 0 ? objects[0] : null; +} +export function getColor(shapeData, position, screenMatrixZoom) { + if (!shapeData) return null; + + const canvas = shapeData.imageData; + const context = canvas.getContext('2d'); + + const start = calculatePointInImage(position, shapeData, screenMatrixZoom); + + const [r, g, b] = context.getImageData(start.x, start.y, 1, 1).data; + const color = (r << 16) + (g << 8) + b; + + return color; +}