Skip to content

Commit

Permalink
progress
Browse files Browse the repository at this point in the history
  • Loading branch information
alexreardon committed May 9, 2018
1 parent 72944e3 commit 2ebe6cc
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 157 deletions.
179 changes: 24 additions & 155 deletions src/state/action-creators.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import type {
ScrollOptions,
Viewport,
DimensionMap,
DropReason,
PendingDrop,
} from '../types';
import noImpact from './no-impact';
import withDroppableDisplacement from './with-droppable-displacement';
Expand Down Expand Up @@ -235,23 +237,9 @@ export type DropAnimateAction = {
|}
}

type AnimateDropArgs = {|
newHomeOffset: Position,
impact: DragImpact,
result: DropResult
|}

const animateDrop = ({
newHomeOffset,
impact,
result,
}: AnimateDropArgs): DropAnimateAction => ({
const animateDrop = (pending: PendingDrop): DropAnimateAction => ({
type: 'DROP_ANIMATE',
payload: {
newHomeOffset,
impact,
result,
},
payload: pending,
});

export type DropCompleteAction = {
Expand All @@ -264,149 +252,29 @@ export const completeDrop = (result: DropResult): DropCompleteAction => ({
payload: result,
});

export const drop = () =>
(dispatch: Dispatch, getState: () => State): void => {
const state: State = getState();

throw new Error('TODO: move to middleware');

// Should not really happen, but oh well
if (state.phase === 'IDLE') {
dispatch(clean());
}

// dropped before a drag officially started - this is fine
if (state.phase === 'PREPARING') {
dispatch(clean());
}

// We cannot drop - we need to wait for the collection to finish
if (state.phase === 'BULK_COLLECTING') {
dispatch(dropAfterCollection());
}

// TODO: blash
if (state.phase === 'DRAGGING') {
dispatch(startDrop());
}

throw new Error(`Cannot drop in phase ${state.phase}`);

// dropped in another phase except for dragging - this is an error
if (state.phase !== 'DRAGGING') {
console.error(`not able to drop in phase: '${state.phase}'`);
dispatch(clean());
}

if (!state.drag) {
console.error('not able to drop when there is invalid drag state', state);
dispatch(clean());
}

const { impact, initial, current } = state.drag;
const descriptor: DraggableDescriptor = initial.descriptor;
const draggable: DraggableDimension = state.dimension.draggable[initial.descriptor.id];
const home: DroppableDimension = state.dimension.droppable[draggable.descriptor.droppableId];
const destination: ?DroppableDimension = impact.destination ?
state.dimension.droppable[impact.destination.droppableId] :
null;

const source: DraggableLocation = {
droppableId: descriptor.droppableId,
index: descriptor.index,
};

const result: DropResult = {
draggableId: descriptor.id,
type: home.descriptor.type,
source,
destination: impact.destination,
reason: 'DROP',
};

const newBorderBoxCenter: Position = getNewHomeClientBorderBoxCenter({
movement: impact.movement,
draggable,
draggables: state.dimension.draggable,
destination,
});

const clientOffset: Position = subtract(newBorderBoxCenter, draggable.client.borderBox.center);
const scrollDiff: Position = getScrollDiff({
initial,
current,
droppable: destination || home,
});
const newHomeOffset: Position = add(clientOffset, scrollDiff);

// Do not animate if you do not need to.
// This will be the case if either you are dragging with a
// keyboard or if you manage to nail it just with a mouse.
const isAnimationRequired = !isEqual(
current.client.offset,
newHomeOffset,
);

if (!isAnimationRequired) {
dispatch(completeDrop(result));
}

dispatch(animateDrop({
newHomeOffset,
impact,
result,
}));
};

export const cancel = () =>
(dispatch: Dispatch, getState: () => State): void => {
const state: State = getState();

// only allowing cancelling in the DRAGGING phase
if (state.phase !== 'DRAGGING') {
dispatch(clean());
return;
}

if (!state.drag) {
console.error('invalid drag state', state);
dispatch(clean());
return;
}

const { initial, current } = state.drag;
const descriptor = initial.descriptor;
const home: DroppableDimension = state.dimension.droppable[descriptor.droppableId];

const source: DraggableLocation = {
index: descriptor.index,
droppableId: descriptor.droppableId,
};

const result: DropResult = {
draggableId: descriptor.id,
type: home.descriptor.type,
source,
// no destination when cancelling
destination: null,
reason: 'CANCEL',
};
type DropArgs = {|
reason: DropReason,
|}

const isAnimationRequired = !isEqual(current.client.offset, origin);
export type DropAction = {|
type: 'DROP',
payload: DropArgs,
|}

if (!isAnimationRequired) {
dispatch(completeDrop(result));
return;
}
export const drop = (args: DropArgs) => ({
type: 'DROP',
payload: args,
});

const scrollDiff: Position = getScrollDiff({ initial, current, droppable: home });
export type DropPendingAction = {|
type: 'DROP_PENDING',
payload: null,
|}

dispatch(animateDrop({
newHomeOffset: scrollDiff,
impact: noImpact,
result,
}));
};
export const dropPending = (): DropPendingAction => ({
type: 'DROP_PENDING',
payload: null,
});

export const dropAnimationFinished = () =>
(dispatch: Dispatch, getState: () => State): void => {
Expand Down Expand Up @@ -439,6 +307,7 @@ export type Action =
MoveForwardAction |
CrossAxisMoveForwardAction |
CrossAxisMoveBackwardAction |
DropAction |
DropAnimateAction |
DropCompleteAction |
PrepareAction |
Expand Down
124 changes: 124 additions & 0 deletions src/state/middleware/drop-middleware.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,126 @@
// @flow
import invariant from 'tiny-invariant';
import type { Position } from 'css-box-model';
import {
dropPending,
completeDrop,
clean,
animateDrop,
} from '../action-creators';
import noImpact from '../no-impact';
import getNewHomeClientBorderBoxCenter from '../get-new-home-client-border-box-center';
import { add, subtract, isEqual } from '../position';
import withDroppableDisplacement from '../with-droppable-displacement';
import type {
Store,
State,
Action,
DropReason,
DroppableDimension,
WindowDetails,
Critical,
DraggableLocation,
DragImpact,
DropResult,
PendingDrop,
DimensionMap,
DraggableDimension,
} from '../../types';

const origin: Position = { x: 0, y: 0 };

const getScrollDisplacement = (
droppable: DroppableDimension,
windowDetails: WindowDetails,
): Position => withDroppableDisplacement(
droppable,
windowDetails.scroll.diff.displacement
);

export default ({ getState, dispatch }: Store) =>
(next: (Action) => mixed) => (action: Action): mixed => {
// TODO: pending drop flushing
if (action.type !== 'DROP') {
next(action);
return;
}

const state: State = getState();
invariant(state.phase === 'DRAGGING' || state.phase === 'BULK_COLLECTING',
`Cannot drop in phase: ${state.phase}`);

// Still waiting for a bulk collection to publish
if (state.phase === 'BULK_COLLECTING') {
dispatch(dropPending());
return;
}

// Was dragging
const reason: DropReason = action.payload.reason;
const critical: Critical = state.critical;
const dimensions: DimensionMap = state.dimensions;
const impact: DragImpact = reason === 'DROP' ? state.impact : noImpact;
const home: DroppableDimension = dimensions.droppables[state.critical.droppable.id];
const draggable: DraggableDimension = dimensions.draggables[state.critical.draggable.id];
const droppable: ?DroppableDimension = impact && impact.destination ?
dimensions.droppables[impact.destination.droppableId] : null;

const source: DraggableLocation = {
index: critical.draggable.index,
droppableId: critical.droppable.id,
};
const destination: ?DraggableLocation = reason === 'DROP' ? impact.destination : null;

const result: DropResult = {
draggableId: draggable.descriptor.id,
type: home.descriptor.type,
source,
destination,
reason,
};

const clientOffset: Position = (() => {
// We are moving back to where we started
if (reason === 'CANCEL') {
return origin;
}

const newBorderBoxCenter: Position = getNewHomeClientBorderBoxCenter({
movement: impact.movement,
draggable,
draggables: dimensions.draggables,
destination: droppable,
});

// What would the offset be from our original center?
return subtract(newBorderBoxCenter, draggable.client.borderBox.center);
})();

const newHomeOffset: Position = add(
clientOffset,
getScrollDisplacement(droppable || home, state.window)
);

// Do not animate if you do not need to.
// This will be the case if either you are dragging with a
// keyboard or if you manage to nail it just with a mouse.
const isAnimationRequired = !isEqual(
state.current.client.offset,
newHomeOffset,
);

const pending: PendingDrop = {
newHomeOffset,
result,
impact,
};

if (isAnimationRequired) {
dispatch(animateDrop(pending));
return;
}

dispatch(completeDrop(result));
dispatch(clean());
};

2 changes: 1 addition & 1 deletion src/state/middleware/style-middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export default (marshal: StyleMarshal) =>
(store: Store) => (next: (Action) => mixed) => (action: Action): mixed => {
const state: State = store.getState();

if (state.phase === ('DRAGGING' || 'BULK_COLLECTING')) {
if (state.phase === 'DRAGGING' || state.phase === 'BULK_COLLECTING')) {
marshal.dragging();
return next(action);
}
Expand Down
1 change: 1 addition & 0 deletions src/view/drag-handle/drag-handle-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {
AutoScrollMode,
Direction,
DraggableId,
DropReason,
} from '../../types';

export type Callbacks = {|
Expand Down
2 changes: 1 addition & 1 deletion src/view/draggable/draggable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export default class Draggable extends Component<Props> {
this.props.moveByWindowScroll({ viewport: getViewport() });
}

onDrop = () => {
onDrop = (reason: DropReason) => {
this.throwIfCannotDrag();
this.props.drop();
}
Expand Down

0 comments on commit 2ebe6cc

Please sign in to comment.