Skip to content

Commit

Permalink
Block Editor: Simplify Selection Reducer
Browse files Browse the repository at this point in the history
  • Loading branch information
ellatrix committed Sep 25, 2019
1 parent a56daeb commit 2fb8d84
Show file tree
Hide file tree
Showing 6 changed files with 405 additions and 498 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -703,7 +703,7 @@ _Returns_
Returns true if the block corresponding to the specified client ID is
currently selected but isn't the last of the selected blocks. Here "last"
refers to the block sequence in the document, _not_ the sequence of
multi-selection, which is why `state.blockSelection.end` isn't used.
multi-selection, which is why `state.selectionEnd` isn't used.

_Parameters_

Expand Down
230 changes: 125 additions & 105 deletions packages/block-editor/src/store/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -922,161 +922,177 @@ export function isCaretWithinFormattedText( state = false, action ) {
return state;
}

const BLOCK_SELECTION_EMPTY_OBJECT = {};
const BLOCK_SELECTION_INITIAL_STATE = {
start: BLOCK_SELECTION_EMPTY_OBJECT,
end: BLOCK_SELECTION_EMPTY_OBJECT,
isMultiSelecting: false,
isEnabled: true,
initialPosition: null,
};

/**
* Reducer returning the block selection's state.
* General reducer returning the block selection's start and end.
*
* @param {Object} state Current state.
* @param {Object} action Dispatched action.
*
* @return {Object} Updated state.
*/
export function blockSelection( state = BLOCK_SELECTION_INITIAL_STATE, action ) {
export function selection( state = {}, action ) {
switch ( action.type ) {
case 'CLEAR_SELECTED_BLOCK':
if ( ! state.start || ! state.start.clientId ) {
return state;
}

return {
...state,
start: BLOCK_SELECTION_EMPTY_OBJECT,
end: BLOCK_SELECTION_EMPTY_OBJECT,
isMultiSelecting: false,
initialPosition: null,
};
case 'START_MULTI_SELECT':
if ( state.isMultiSelecting ) {
return state;
}

return {
...state,
isMultiSelecting: true,
initialPosition: null,
};
case 'STOP_MULTI_SELECT':
if ( ! state.isMultiSelecting ) {
return state;
case 'CLEAR_SELECTED_BLOCK': {
if ( state.clientId ) {
return {};
}

return {
...state,
isMultiSelecting: false,
initialPosition: null,
};
case 'MULTI_SELECT':
return {
...state,
isMultiSelecting: state.isMultiSelecting,
start: { clientId: action.start },
end: { clientId: action.end },
};
return state;
}
case 'SELECT_BLOCK':
if (
action.clientId === state.start.clientId &&
action.clientId === state.end.clientId
) {
if ( action.clientId === state.clientId ) {
return state;
}

return {
...state,
initialPosition: action.initialPosition,
start: { clientId: action.clientId },
end: { clientId: action.clientId },
};
return { clientId: action.clientId };
case 'REPLACE_INNER_BLOCKS': // REPLACE_INNER_BLOCKS and INSERT_BLOCKS should follow the same logic.
case 'INSERT_BLOCKS': {
if ( action.updateSelection ) {
return {
...state,
start: { clientId: action.blocks[ 0 ].clientId },
end: { clientId: action.blocks[ 0 ].clientId },
};
if ( ! action.updateSelection ) {
return state;
}

return state;
return { clientId: action.blocks[ 0 ].clientId };
}
case 'REMOVE_BLOCKS':
if (
! action.clientIds ||
! action.clientIds.length ||
action.clientIds.indexOf( state.start.clientId ) === -1
action.clientIds.indexOf( state.clientId ) === -1
) {
return state;
}

return {
...state,
start: BLOCK_SELECTION_EMPTY_OBJECT,
end: BLOCK_SELECTION_EMPTY_OBJECT,
isMultiSelecting: false,
initialPosition: null,
};
return {};
case 'REPLACE_BLOCKS': {
if ( action.clientIds.indexOf( state.start.clientId ) === -1 ) {
if ( action.clientIds.indexOf( state.clientId ) === -1 ) {
return state;
}

const indexToSelect = action.indexToSelect || action.blocks.length - 1;
const blockToSelect = action.blocks[ indexToSelect ];

if ( ! blockToSelect ) {
return {
...state,
start: BLOCK_SELECTION_EMPTY_OBJECT,
end: BLOCK_SELECTION_EMPTY_OBJECT,
isMultiSelecting: false,
initialPosition: null,
};
return {};
}

if (
blockToSelect.clientId === state.start.clientId &&
blockToSelect.clientId === state.end.clientId
) {
if ( blockToSelect.clientId === state.clientId ) {
return state;
}

return {
...state,
start: { clientId: blockToSelect.clientId },
end: { clientId: blockToSelect.clientId },
};
return { clientId: blockToSelect.clientId };
}
case 'TOGGLE_SELECTION':
}

return state;
}

/**
* Reducer returning the block selection's start.
*
* @param {Object} state Current state.
* @param {Object} action Dispatched action.
*
* @return {Object} Updated state.
*/
export function selectionStart( state = {}, action ) {
switch ( action.type ) {
case 'SELECTION_CHANGE':
return {
...state,
isEnabled: action.isSelectionEnabled,
clientId: action.clientId,
attributeKey: action.attributeKey,
offset: action.startOffset,
};
case 'RESET_SELECTION':
return action.selectionStart;
case 'MULTI_SELECT':
return { clientId: action.start };
}

return selection( state, action );
}

/**
* Reducer returning the block selection's end.
*
* @param {Object} state Current state.
* @param {Object} action Dispatched action.
*
* @return {Object} Updated state.
*/
export function selectionEnd( state = {}, action ) {
switch ( action.type ) {
case 'SELECTION_CHANGE':
return {
...state,
start: {
clientId: action.clientId,
attributeKey: action.attributeKey,
offset: action.startOffset,
},
end: {
clientId: action.clientId,
attributeKey: action.attributeKey,
offset: action.endOffset,
},
clientId: action.clientId,
attributeKey: action.attributeKey,
offset: action.endOffset,
};
case 'RESET_SELECTION':
return action.selectionEnd;
case 'MULTI_SELECT':
return { clientId: action.end };
}

return selection( state, action );
}

/**
* Reducer returning whether the user is multi-selecting.
*
* @param {boolean} state Current state.
* @param {Object} action Dispatched action.
*
* @return {boolean} Updated state.
*/
export function isMultiSelecting( state = false, action ) {
switch ( action.type ) {
case 'START_MULTI_SELECT':
return true;

case 'STOP_MULTI_SELECT':
return false;
}

return state;
}

/**
* Reducer returning whether selection is enabled.
*
* @param {boolean} state Current state.
* @param {Object} action Dispatched action.
*
* @return {boolean} Updated state.
*/
export function isSelectionEnabled( state = true, action ) {
switch ( action.type ) {
case 'TOGGLE_SELECTION':
return action.isSelectionEnabled;
}

return state;
}

/**
* Reducer returning the intial block selection.
*
* Currently this in only used to restore the selection after block deletion.
* This reducer should eventually be removed in favour of setting selection
* directly.
*
* @param {boolean} state Current state.
* @param {Object} action Dispatched action.
*
* @return {?number} Initial position: -1 or undefined.
*/
export function initialPosition( state, action ) {
if ( action.type === 'SELECT_BLOCK' ) {
return action.initialPosition;
} else if ( action.type === 'REMOVE_BLOCKS' ) {
return state;
}
}

export function blocksMode( state = {}, action ) {
if ( action.type === 'TOGGLE_BLOCK_MODE' ) {
const { clientId } = action;
Expand Down Expand Up @@ -1303,7 +1319,11 @@ export default combineReducers( {
blocks,
isTyping,
isCaretWithinFormattedText,
blockSelection,
selectionStart,
selectionEnd,
isMultiSelecting,
isSelectionEnabled,
initialPosition,
blocksMode,
blockListSettings,
insertionPoint,
Expand Down
Loading

0 comments on commit 2fb8d84

Please sign in to comment.