/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at . */
// @flow
/**
* Breakpoints reducer
* @module reducers/breakpoints
*/
import { isGeneratedId } from "devtools-source-map";
import { isEqual } from "lodash";
import { makeBreakpointId } from "../utils/breakpoint";
// eslint-disable-next-line max-len
import { getBreakpointsList as getBreakpointsListSelector } from "../selectors/breakpoints";
import type {
XHRBreakpoint,
Breakpoint,
BreakpointId,
SourceLocation
} from "../types";
import type { Action } from "../actions/types";
export type BreakpointsMap = { [BreakpointId]: Breakpoint };
export type XHRBreakpointsList = $ReadOnlyArray;
export type BreakpointsState = {
breakpoints: BreakpointsMap,
xhrBreakpoints: XHRBreakpointsList,
breakpointsDisabled: boolean
};
export function initialBreakpointsState(
xhrBreakpoints?: XHRBreakpointsList = []
): BreakpointsState {
return {
breakpoints: {},
xhrBreakpoints: xhrBreakpoints,
breakpointsDisabled: false
};
}
function update(
state: BreakpointsState = initialBreakpointsState(),
action: Action
): BreakpointsState {
switch (action.type) {
case "SET_BREAKPOINT": {
return setBreakpoint(state, action);
}
case "REMOVE_BREAKPOINT": {
return removeBreakpoint(state, action);
}
case "NAVIGATE": {
return initialBreakpointsState(state.xhrBreakpoints);
}
case "SET_XHR_BREAKPOINT": {
return addXHRBreakpoint(state, action);
}
case "REMOVE_XHR_BREAKPOINT": {
return removeXHRBreakpoint(state, action);
}
case "UPDATE_XHR_BREAKPOINT": {
return updateXHRBreakpoint(state, action);
}
case "ENABLE_XHR_BREAKPOINT": {
return updateXHRBreakpoint(state, action);
}
case "DISABLE_XHR_BREAKPOINT": {
return updateXHRBreakpoint(state, action);
}
}
return state;
}
function addXHRBreakpoint(state, action) {
const { xhrBreakpoints } = state;
const { breakpoint } = action;
const { path, method } = breakpoint;
const existingBreakpointIndex = state.xhrBreakpoints.findIndex(
bp => bp.path === path && bp.method === method
);
if (existingBreakpointIndex === -1) {
return {
...state,
xhrBreakpoints: [...xhrBreakpoints, breakpoint]
};
} else if (xhrBreakpoints[existingBreakpointIndex] !== breakpoint) {
const newXhrBreakpoints = [...xhrBreakpoints];
newXhrBreakpoints[existingBreakpointIndex] = breakpoint;
return {
...state,
xhrBreakpoints: newXhrBreakpoints
};
}
return state;
}
function removeXHRBreakpoint(state, action) {
const { breakpoint } = action;
const { xhrBreakpoints } = state;
if (action.status === "start") {
return state;
}
return {
...state,
xhrBreakpoints: xhrBreakpoints.filter(bp => !isEqual(bp, breakpoint))
};
}
function updateXHRBreakpoint(state, action) {
const { breakpoint, index } = action;
const { xhrBreakpoints } = state;
const newXhrBreakpoints = [...xhrBreakpoints];
newXhrBreakpoints[index] = breakpoint;
return {
...state,
xhrBreakpoints: newXhrBreakpoints
};
}
function setBreakpoint(state, { breakpoint }): BreakpointsState {
const id = makeBreakpointId(breakpoint.location);
const breakpoints = { ...state.breakpoints, [id]: breakpoint };
return { ...state, breakpoints };
}
function removeBreakpoint(state, { location }): BreakpointsState {
const id = makeBreakpointId(location);
const breakpoints = { ...state.breakpoints };
delete breakpoints[id];
return { ...state, breakpoints };
}
function isMatchingLocation(location1, location2) {
return isEqual(location1, location2);
}
// Selectors
// TODO: these functions should be moved out of the reducer
type OuterState = { breakpoints: BreakpointsState };
export function getBreakpointsMap(state: OuterState): BreakpointsMap {
return state.breakpoints.breakpoints;
}
export function getBreakpointsList(state: OuterState): Breakpoint[] {
return getBreakpointsListSelector((state: any));
}
export function getBreakpointCount(state: OuterState): number {
return getBreakpointsList(state).length;
}
export function getBreakpoint(
state: OuterState,
location: SourceLocation
): ?Breakpoint {
const breakpoints = getBreakpointsMap(state);
return breakpoints[makeBreakpointId(location)];
}
export function getBreakpointsDisabled(state: OuterState): boolean {
const breakpoints = getBreakpointsList(state);
return breakpoints.every(breakpoint => breakpoint.disabled);
}
export function getBreakpointsForSource(
state: OuterState,
sourceId: string,
line: ?number
): Breakpoint[] {
if (!sourceId) {
return [];
}
const isGeneratedSource = isGeneratedId(sourceId);
const breakpoints = getBreakpointsList(state);
return breakpoints.filter(bp => {
const location = isGeneratedSource ? bp.generatedLocation : bp.location;
return location.sourceId === sourceId && (!line || line == location.line);
});
}
export function getBreakpointForLocation(
state: OuterState,
location: SourceLocation | null
): ?Breakpoint {
if (!location || !location.sourceId) {
return undefined;
}
const isGeneratedSource = isGeneratedId(location.sourceId);
return getBreakpointsList(state).find(bp => {
const loc = isGeneratedSource ? bp.generatedLocation : bp.location;
return isMatchingLocation(loc, location);
});
}
export function getHiddenBreakpoint(state: OuterState): ?Breakpoint {
const breakpoints = getBreakpointsList(state);
return breakpoints.find(bp => bp.options.hidden);
}
export default update;