Skip to content

Commit

Permalink
[editor] Move event mapping and meta extraction from editor to intera…
Browse files Browse the repository at this point in the history
…ction

This means Redux events are serializable now.
Also a step towards edit policies, as there's less domain-specific logic in tools.
A step towards #2
  • Loading branch information
Izhaki committed Jan 14, 2020

Verified

This commit was signed with the committer’s verified signature.
psafont Pau Ruiz Safont
1 parent 1d89403 commit 8266f84
Showing 13 changed files with 76 additions and 60 deletions.
6 changes: 3 additions & 3 deletions examples/editor/basic-drag-editor/moveTool.js
Original file line number Diff line number Diff line change
@@ -12,9 +12,9 @@ export default () => {
return next => action => {
switch (action.type) {
case 'mouseDown': {
const meta = getDomainMeta(action.event.target);
if (isValidDragSource(meta)) {
dragged = meta.id;
const target = getDomainMeta(action.event.target);
if (isValidDragSource(target)) {
dragged = target.id;
} else {
return false; // Cancel drag
}
9 changes: 8 additions & 1 deletion examples/editor/chip-editor/chip-editor.jsx
Original file line number Diff line number Diff line change
@@ -7,10 +7,17 @@ import multiTool from './tools/multiTool';
import { fader, lfo, filter } from './chips';
import { targetifyNode, targetifyConnection } from './targetify';
import Actions from './components/actions/Actions';
import getDomainTarget from './getDomainTarget';

const eventMapper = (event, props) => ({
target: getDomainTarget(event.target, props),
delta: event.getDelta && event.getDelta(),
position: event.getPosition(),
});

const Graph = graph({
connector: connectGraph({ autoBox: true }),
interactive: true,
interactive: eventMapper,
layout: true,
autoBox: true,
connection: {
35 changes: 21 additions & 14 deletions examples/editor/chip-editor/tools/connectionTool.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { find } from '../../../../packages/editor/src/slices/utils';
import getDomainMeta from '../getDomainMeta';
import isValidConnection from '../isValidConnection';
import {
addConnection,
@@ -17,49 +16,57 @@ const getEndId = end => `${end.id}/${end.port}`;
const generateId = ({ src, dst }) => `${getEndId(src)}->${getEndId(dst)}`;

export default ({ getState }) => {
let srcMeta = null;
let source = null;
return next => action => {
switch (action.type) {
case 'mouseDown': {
const { connections } = getState();
const dstMeta = action.meta;
srcMeta = dstMeta;
const isValid = isValidConnection(srcMeta, dstMeta, connections);
const { target } = action.event;
source = target;
const isValid = isValidConnection(source, target, connections);

const [from, to] =
srcMeta.type === 'output' ? ['src', 'dst'] : ['dst', 'src'];
const end = getEnd(srcMeta);
source.type === 'output' ? ['src', 'dst'] : ['dst', 'src'];
const end = getEnd(source);

return next(
addConnection({
id: '@@draggedConnection',
[from]: end,
[to]: isValid ? end : action.event.getPosition(),
[to]: isValid ? end : action.event.position,
})
);
}

case 'mouseMove': {
const state = getState();
const dstMeta = getDomainMeta(action.event.target, state);
const isValid = isValidConnection(srcMeta, dstMeta, state.connections);
const isValid = isValidConnection(
source,
action.event.target,
state.connections
);

const end = srcMeta.type === 'output' ? 'dst' : 'src';
const end = source.type === 'output' ? 'dst' : 'src';

return next(
updateConnections({
ids: ['@@draggedConnection'],
updates: {
[end]: isValid ? getEnd(dstMeta) : action.event.getPosition(),
[end]: isValid
? getEnd(action.event.target)
: action.event.position,
},
})
);
}

case 'mouseUp': {
const state = getState();
const dstMeta = getDomainMeta(action.event.target, state);
const isValid = isValidConnection(srcMeta, dstMeta, state.connections);
const isValid = isValidConnection(
source,
action.event.target,
state.connections
);

if (!isValid) {
return next(
4 changes: 2 additions & 2 deletions examples/editor/chip-editor/tools/moveTool.js
Original file line number Diff line number Diff line change
@@ -5,15 +5,15 @@ export default () => {
return next => action => {
switch (action.type) {
case 'mouseDown': {
dragged = action.meta.id;
dragged = action.event.target.id;
break;
}

case 'mouseMove': {
return next(
moveBox({
id: dragged,
delta: action.event.getDelta(),
delta: action.event.delta,
})
);
}
13 changes: 3 additions & 10 deletions examples/editor/chip-editor/tools/multiTool.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
import getDomainMeta from '../getDomainMeta';
import connectionTool from './connectionTool';
import moveTool from './moveTool';
import selectionTool from './selectionTool';

export default store => {
let currentTool = null;

const getMeta = action => {
const state = store.getState();
return getDomainMeta(action.event.target, state);
};

const tools = {
connection: connectionTool(store),
move: moveTool(store),
@@ -26,11 +20,10 @@ export default store => {
return next => action => {
switch (action.type) {
case 'mouseDown': {
const meta = getMeta(action);
tools.selection(next)(action);
if (meta.draggable) {
action.meta = meta;
currentTool = targetTypeToTool[meta.type];
const { target } = action.event;
if (target.draggable) {
currentTool = targetTypeToTool[target.type];
return currentTool(next)(action);
}
break;
23 changes: 11 additions & 12 deletions examples/editor/chip-editor/tools/selectionTool.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import getDomainMeta from '../getDomainMeta';
import {
select,
deselect,
@@ -31,35 +30,35 @@ const deselectAll = (dispatch, selected) => {
})
);

return dispatch(deselect({ metas: selected, all: true }));
return dispatch(deselect({ targets: selected, all: true }));
};

export default ({ getState, dispatch }) => next => action => {
switch (action.type) {
case 'mouseDown': {
const state = getState();
const meta = getDomainMeta(action.event.target, state);
if (meta.selectable) {
deselectAll(dispatch, state.selected);
if (isConnection(meta)) {
const { selected } = getState();
const { target } = action.event;
if (target.selectable) {
deselectAll(dispatch, selected);
if (isConnection(target)) {
dispatch(
updateConnections({
ids: [meta.id],
ids: [target.id],
updates: { selected: true },
})
);
}
if (isNode(meta)) {
if (isNode(target)) {
dispatch(
updateNodes({
ids: [meta.id],
ids: [target.id],
updates: { selected: true },
})
);
}
return next(select({ metas: [meta] }));
return next(select({ targets: [target] }));
}
return deselectAll(next, state.selected);
return deselectAll(next, selected);
}
default:
}
3 changes: 1 addition & 2 deletions packages/editor/src/editor.jsx
Original file line number Diff line number Diff line change
@@ -8,8 +8,7 @@ export default ({ initialState, tool }) => {
const store = configureStore({
reducer,
preloadedState: initialState,
devTools: false,
middleware: [...getDefaultMiddleware({ serializableCheck: false }), tool],
middleware: [...getDefaultMiddleware(), tool],
});

const Editor = ({ children }) => (
4 changes: 2 additions & 2 deletions packages/editor/src/slices/selected.js
Original file line number Diff line number Diff line change
@@ -5,8 +5,8 @@ export default createSlice({
initialState: [],
reducers: {
select(selected, action) {
const { metas } = action.payload;
selected.push(...metas);
const { targets } = action.payload;
selected.push(...targets);
},
deselect(selected, action) {
const { all } = action.payload;
14 changes: 8 additions & 6 deletions packages/editor/src/thunks/deleteSelected.js
Original file line number Diff line number Diff line change
@@ -10,10 +10,12 @@ const getNodeConnectionsIds = ({ connections, nodeIds, connectionIds }) => {
nodeIds.some(
nodeId => connection.src.id === nodeId || connection.dst.id === nodeId
);
return connections.filter(
connection =>
notSelected(connection) && isSelectedNodeConnection(connection)
);
return connections
.filter(
connection =>
notSelected(connection) && isSelectedNodeConnection(connection)
)
.map(getId);
};

export default () => (dispatch, getState) => {
@@ -33,8 +35,8 @@ export default () => (dispatch, getState) => {
})
);

// Providing empty `metas` means connections and nodes won't remove the
// Providing empty `targets` means connections and nodes won't remove the
// selected flag from the (already deleted) items.
// `all` will clear the `selected` array.
dispatch(deselect({ metas: [], all: true }));
dispatch(deselect({ targets: [], all: true }));
};
3 changes: 2 additions & 1 deletion packages/graph/src/graph.js
Original file line number Diff line number Diff line change
@@ -78,7 +78,8 @@ export default ({
hocs.push(connector);
}
if (interactive) {
hocs.push(withInteraction);
const eventMapper = interactive === true ? undefined : interactive;
hocs.push(withInteraction(eventMapper));
}
if (autoBox) {
hocs.push(withAutoBox);
16 changes: 12 additions & 4 deletions packages/graph/src/highs/interaction/useInteraction.js
Original file line number Diff line number Diff line change
@@ -20,14 +20,22 @@ const useInteraction = compose(
);

export default ({
eventMapper = event => event,
ref,
onMouseDown = noop,
onMouseMove = noop,
onMouseUp = noop,
props,
}) => {
const onMouseDownStable = useEventCallback(onMouseDown);
const onMouseMoveStable = useEventCallback(onMouseMove);
const onMouseUpStable = useEventCallback(onMouseUp);
const onMouseDownStable = useEventCallback(event =>
onMouseDown(eventMapper(event, props))
);
const onMouseMoveStable = useEventCallback(event =>
onMouseMove(eventMapper(event, props))
);
const onMouseUpStable = useEventCallback(event =>
onMouseUp(eventMapper(event, props))
);

const interactionProps = useMemo(
() =>
@@ -37,7 +45,7 @@ export default ({
onMouseMove: onMouseMoveStable,
onMouseUp: onMouseUpStable,
}),
[onMouseUpStable, onMouseMoveStable, onMouseDownStable, ref]
[ref, onMouseDownStable, onMouseMoveStable, onMouseUpStable]
);

const clickAwayRef = useMouseAway({
6 changes: 3 additions & 3 deletions packages/graph/src/highs/interaction/withInteraction.jsx
Original file line number Diff line number Diff line change
@@ -2,21 +2,21 @@ import React, { useRef } from 'react';
import PropTypes from 'prop-types';
import useInteraction from './useInteraction';

export default WrappedComponent => {
export default eventMapper => WrappedComponent => {
const WithInteraction = ({
onMouseDown,
onMouseMove,
onMouseUp,
onClick,
...props
}) => {
const ref = useRef(null);
const interactionProps = useInteraction({
eventMapper,
ref,
onClick,
onMouseDown,
onMouseMove,
onMouseUp,
props,
});

return <WrappedComponent {...interactionProps} {...props} />;

0 comments on commit 8266f84

Please sign in to comment.