mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Popups
synced 2024-12-04 03:58:41 +00:00
e4719c4918
Action creator changes: * Make the linkAbandon action creator asynchronous by splitting it into two distinct actions, LINK_ABANDON_START and _END, the latter of which is dispatched after a 300 ms delay. * Introduce the previewDwell and previewAbandon action creators. The latter is an asynchronous action that mirrors the linkAbandon action. Reducer changes: * Make the LINK_DWELL, LINK_ABANDON_END, and PREVIEW_ABANDON_END action hide a preview, if one has been shown. * Make the LINK_ABANDON_END action NOOP if: * The user has interacted with another link, or * The user is interacting with the preview. Supporting changes: * Update the mw.popups.reducers#preview and #renderer unit tests to use an empty previous state so that the tests are more resilient to modifications of the state tree. Change-Id: I2ecf575bbb59bb64772f75da9b5a29c071b46a8d
178 lines
4.7 KiB
JavaScript
178 lines
4.7 KiB
JavaScript
( function ( mw, $ ) {
|
|
mw.popups.reducers = {};
|
|
|
|
/**
|
|
* Creates the next state tree from the current state tree and some updates.
|
|
*
|
|
* In [change listeners](/doc/change_listeners.md), for example, we talk about
|
|
* the previous state and the current state (the `prevState` and `state`
|
|
* parameters, respectively). Since
|
|
* [reducers](http://redux.js.org/docs/basics/Reducers.html) take the current
|
|
* state and an action and make updates, "next state" seems appropriate.
|
|
*
|
|
* @param {Object} state
|
|
* @param {Object} updates
|
|
* @return {Object}
|
|
*/
|
|
function nextState( state, updates ) {
|
|
var result = $.extend( {}, state ),
|
|
key;
|
|
|
|
for ( key in updates ) {
|
|
if ( updates.hasOwnProperty( key ) ) {
|
|
result[key] = updates[key];
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Reducer for actions that modify the state of the preview model
|
|
*
|
|
* @param {Object} state before action
|
|
* @param {Object} action Redux action that modified state.
|
|
* Must have `type` property.
|
|
* @return {Object} state after action
|
|
*/
|
|
mw.popups.reducers.preview = function ( state, action ) {
|
|
if ( state === undefined ) {
|
|
state = {
|
|
enabled: undefined,
|
|
sessionToken: undefined,
|
|
pageToken: undefined,
|
|
linkInteractionToken: undefined,
|
|
activeLink: undefined,
|
|
activeEvent: undefined,
|
|
interactionStarted: undefined,
|
|
shouldShow: false,
|
|
isUserDwelling: false
|
|
};
|
|
}
|
|
|
|
switch ( action.type ) {
|
|
case mw.popups.actionTypes.BOOT:
|
|
return nextState( state, {
|
|
enabled: action.isUserInCondition,
|
|
sessionToken: action.sessionToken,
|
|
pageToken: action.pageToken
|
|
} );
|
|
case mw.popups.actionTypes.LINK_DWELL:
|
|
return nextState( state, {
|
|
activeLink: action.el,
|
|
activeEvent: action.event,
|
|
interactionStarted: action.interactionStarted,
|
|
linkInteractionToken: action.linkInteractionToken,
|
|
|
|
// When the user dwells on a link with their keyboard, a preview is
|
|
// renderered, and then dwells on another link, the LINK_ABANDON_END
|
|
// action will be ignored.
|
|
//
|
|
// Ensure that all the preview is hidden.
|
|
shouldShow: false
|
|
} );
|
|
case mw.popups.actionTypes.LINK_ABANDON_END:
|
|
if ( action.el !== state.activeLink ) {
|
|
return state;
|
|
}
|
|
|
|
/* falls through */
|
|
case mw.popups.actionTypes.PREVIEW_ABANDON_END:
|
|
if ( !state.isUserDwelling ) {
|
|
return nextState( state, {
|
|
activeLink: undefined,
|
|
activeEvent: undefined,
|
|
interactionStarted: undefined,
|
|
linkInteractionToken: undefined,
|
|
fetchResponse: undefined,
|
|
shouldShow: false
|
|
} );
|
|
}
|
|
|
|
/* falls through */
|
|
case mw.popups.actionTypes.PREVIEW_DWELL:
|
|
return nextState( state, {
|
|
isUserDwelling: true
|
|
} );
|
|
case mw.popups.actionTypes.PREVIEW_ABANDON_START:
|
|
return nextState( state, {
|
|
isUserDwelling: false
|
|
} );
|
|
case mw.popups.actionTypes.FETCH_START:
|
|
return nextState( state, {
|
|
fetchResponse: undefined
|
|
} );
|
|
case mw.popups.actionTypes.FETCH_END:
|
|
if ( action.el === state.activeLink ) {
|
|
return nextState( state, {
|
|
fetchResponse: action.result,
|
|
shouldShow: true
|
|
} );
|
|
}
|
|
|
|
/* falls through */
|
|
default:
|
|
return state;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Reducer for actions that modify the state of the view
|
|
*
|
|
* @param {Object} state before action
|
|
* @param {Object} action Redux action that modified state.
|
|
* Must have `type` property.
|
|
* @return {Object} state after action
|
|
*/
|
|
mw.popups.reducers.renderer = function ( state, action ) {
|
|
if ( state === undefined ) {
|
|
state = {
|
|
isAnimating: false,
|
|
isInteractive: false,
|
|
showSettings: false
|
|
};
|
|
}
|
|
|
|
switch ( action.type ) {
|
|
case mw.popups.actionTypes.PREVIEW_ANIMATING:
|
|
return $.extend( {}, state, {
|
|
isAnimating: true,
|
|
isInteractive: false,
|
|
showSettings: false
|
|
} );
|
|
case mw.popups.actionTypes.PREVIEW_INTERACTIVE:
|
|
return $.extend( OO.copy( state ), {
|
|
isAnimating: false,
|
|
isInteractive: true,
|
|
showSettings: false
|
|
} );
|
|
case mw.popups.actionTypes.PREVIEW_CLICK:
|
|
return $.extend( OO.copy( state ), {
|
|
isAnimating: false,
|
|
isInteractive: false,
|
|
showSettings: false
|
|
} );
|
|
case mw.popups.actionTypes.COG_CLICK:
|
|
return $.extend( OO.copy( state ), {
|
|
isAnimating: true,
|
|
isInteractive: false,
|
|
showSettings: true
|
|
} );
|
|
case mw.popups.actionTypes.SETTINGS_DIALOG_INTERACTIVE:
|
|
return $.extend( OO.copy( state ), {
|
|
isAnimating: false,
|
|
isInteractive: true,
|
|
showSettings: true
|
|
} );
|
|
case mw.popups.actionTypes.SETTINGS_DIALOG_CLOSED:
|
|
return $.extend( OO.copy( state ), {
|
|
isAnimating: false,
|
|
isInteractive: false,
|
|
showSettings: false
|
|
} );
|
|
default:
|
|
return state;
|
|
}
|
|
};
|
|
}( mediaWiki, jQuery ) );
|