in clns-acuity-vahub/vahub/src/main/webapp/src/app/plugins/timeline/store/TimelineReducer.ts [65:657]
export function timelineReducer(currentState: AppStore = initialState, actionToApply: ActionWithPayload<any>): Map<string, any> {
switch (actionToApply.type) {
case CHANGE_STUDY_SELECTION:
return changeStudySelection(currentState, actionToApply);
case UPDATE_INITIAL_OPENING_STATE:
return setInitialOpeningState(currentState, actionToApply);
case APPLY_INITIAL_OPENING_STATE:
return applyInitialOpeningState(currentState, actionToApply);
case SAVE_TRACK_INITIAL_OPENING_STATE:
return saveTrackInitialOpeningState(currentState, actionToApply);
case UPDATE_POSSIBLE_SUBJECTS:
return setPossibleSubjects(currentState, actionToApply);
case POSSIBLE_TRACKS:
return setPossibleTracks(currentState, actionToApply);
case RESET:
return resetState();
case LOADING:
return setLoading(currentState, actionToApply);
case CHANGE_TRACKS:
return changeTracks(currentState, actionToApply);
case SHOW_TRACK:
return showTrack(currentState, actionToApply);
case HIDE_TRACK:
return hideTrack(currentState, actionToApply);
case UPDATE_DATA:
return updateData(currentState, actionToApply);
case CHANGE_PAGE:
return changePage(currentState, actionToApply);
case APPLY_ZOOM:
return updateZoom(currentState, actionToApply);
case EXPAND_TRACK:
case COLLAPSE_TRACK:
return expandOrCollapseTrack(currentState, actionToApply);
case CHANGE_LABS_YAXIS_VALUE:
return changeLabsYAxisValue(currentState, actionToApply);
case CHANGE_SPIROMETRY_YAXIS_VALUE:
return changeSpirometryYAxisValue(currentState, actionToApply);
case CHANGE_ECG_YAXIS_VALUE:
return changeEcgYAxisValue(currentState, actionToApply);
case CHANGE_ECG_WARNINGS:
return changeEcgWarnings(currentState, actionToApply);
case CHANGE_VITALS_YAXIS_VALUE:
return changeVitalsYAxisValue(currentState, actionToApply);
case CHANGE_DAY_ZERO:
return changeDayZero(currentState, actionToApply);
case CHANGE_PERFORMED_JUMP_FLAG:
return changePerformedJumpFlag(currentState, actionToApply);
case CHANGE_DAY_ZERO_OPTIONS:
return changeDayZeroOptions(currentState, actionToApply);
case UPDATE_PLOT_BANDS:
return updatePlotBands(currentState, actionToApply);
case UPDATE_INITIALIZED:
return changedIsInitialized(currentState, actionToApply);
default:
return currentState;
}
/**
* Change study selection by clearing existing initial opening state
*/
function changeStudySelection(state: AppStore, action: ActionWithPayload<{timelineId: TimelineId}>): Map<string, any> {
return state.setIn(['initialOpeningState', action.payload.timelineId],
initialState.getIn(['initialOpeningState', action.payload.timelineId]));
}
/**
* Set the initial opening state of the timeline plot
*/
function setInitialOpeningState(state: AppStore, action: ActionWithPayload<any>): Map<string, any> {
return state.withMutations(map => {
const tracks: ITrack[] = action.payload.tracks.map((track) => {
return new TrackRecord({
name: track.name,
order: track.order,
expansionLevel: track.expansionLevel,
selected: true,
data: []
});
});
map.setIn(['initialOpeningState', action.payload.timelineId, 'tracks'], List<ITrack>(tracks));
map.setIn(['timelines', action.payload.timelineId, 'performedJumpToTimeline'], action.payload.performedJumpToTimeline);
map.setIn(['timelines', action.payload.timelineId, 'isInitialized'], action.payload.isInitialized);
map.setIn(['timelines', action.payload.timelineId, 'tracks'], List<ITrack>());
map.setIn(['timelines', action.payload.timelineId, 'subjects'], Map<string, ISubject>());
map.setIn(['timelines', action.payload.timelineId, 'displayedSubjects'], List<string>());
});
}
/**
* Save the initial opening state of the timeline plot
* 1. collect opening track details
* 2. set track opening state in the initial opening state
*/
function saveTrackInitialOpeningState(state: AppStore, action: ActionWithPayload<{timelineId: TimelineId}>): Map<string, any> {
return state.withMutations(map => {
// get tracks first
const tracks: List<ITrack> = map.getIn(['timelines', action.payload.timelineId, 'tracks']);
const initialTracks = tracks.filter((track) => {
return track.selected;
}).map((track) => {
return new TrackRecord({
name: track.name,
order: track.order,
expansionLevel: track.expansionLevel,
selected: track.selected,
data: []
});
});
map.setIn(['initialOpeningState', action.payload.timelineId, 'tracks'], initialTracks);
});
}
/**
* Set possible tracks for the timeline plot
*/
function setPossibleTracks(state: AppStore, action: ActionWithPayload<PossibleTracks>): Map<string, any> {
const tracks = action.payload.tracks.map((track) => {
return new TrackRecord({
name: track.name,
order: null,
expansionLevel: track.expansionLevel,
selected: track.selected,
data: []
});
});
return state.setIn(['timelines', action.payload.timelineId, 'tracks'], List<ITrack>(tracks));
}
/**
* Apply the initial opening state to the timeline plot state
* 1. update each track in timeline
*/
function applyInitialOpeningState(state: AppStore, action: ActionWithPayload<{timelineId: TimelineId}>): Map<string, any> {
return state.withMutations(map => {
// add empty tracks to possible tracks
const tracks: List<ITrack> = map.getIn(['timelines', action.payload.timelineId, 'tracks']);
const initialOpeningStateTracks: List<ITrack> =
map.getIn(['initialOpeningState', action.payload.timelineId, 'tracks']);
const newTracks: List<ITrack> = <List<ITrack>> tracks.map((track: ITrack) => {
const initialOpeningStateTrack = initialOpeningStateTracks.find((initialTrack) => {
return initialTrack.name === track.name;
});
if (initialOpeningStateTrack) {
return new TrackRecord({
name: track.name,
order: track.order ? track.order : initialOpeningStateTrack.order,
expansionLevel: initialOpeningStateTrack.expansionLevel,
selected: true,
data: []
});
} else {
return track;
}
});
map.setIn(['timelines', action.payload.timelineId, 'tracks'], newTracks);
});
}
/**
* Set the possible subjects
* 1. set possible subjects
* 2. reset page
* 3. clear displayed subjects
*/
function setPossibleSubjects(state: AppStore, action: ActionWithPayload<PossibleSubjects>): Map<string, any> {
return state.withMutations(map => {
const tracks: List<ITrack> = map.getIn(['timelines', action.payload.timelineId, 'tracks']);
const selectedTracks: List<ITrack> = <List<ITrack>> tracks.filter((track: ITrack) => {
return track.selected;
});
// set possible subjects
const possibleSubjects = action.payload.subjects.reduce((possibleSubject, subjectId) => {
let subjectTracks = selectedTracks.map((track: ITrack) => {
return track.set('data', []);
});
// update the track expandsions
const originalSubject = map.getIn(['timelines', action.payload.timelineId, 'subjects', subjectId]);
if (originalSubject) {
subjectTracks = <List<ITrack>>subjectTracks.map((subjectTrack: ITrack) => {
const track: ITrack = originalSubject.tracks.find(item => {
return item.name === subjectTrack.name;
});
if (track) {
return subjectTrack.setIn(['expansionLevel'], track.expansionLevel);
} else {
return subjectTrack;
}
});
}
return possibleSubject.set(subjectId, new SubjectRecord({
subjectId: subjectId,
tracks: subjectTracks
}));
}, OrderedMap<string, ISubject>());
map.setIn(['timelines', action.payload.timelineId, 'subjects'], possibleSubjects);
// reset page
map.setIn(['timelines', action.payload.timelineId, 'page'], new PageRecord({
limit: -1,
offset: -1
}));
// clear displayed subjects
map.setIn(['timelines', action.payload.timelineId, 'displayedSubjects'], List<string>());
});
}
/**
* Set the state of the timeline plot to initial state when leaving currently viewed study
*/
function resetState(): Map<string, any> {
return initialState;
}
/**
* Set the state of the timeline plot to loading
*/
function setLoading(state: AppStore, action: ActionWithPayload<{timelineId: TimelineId, loading: boolean}>): Map<string, any> {
return state.setIn(['timelines', action.payload.timelineId, 'loading'], action.payload.loading);
}
/**
* Select and deselect multiple tracks
*/
function changeTracks(state: AppStore, action: ActionWithPayload<any>): Map<string, any> {
let currState: AppStore = state;
for (const track in action.payload.tracks) {
if (action.payload.tracks[track].selected) {
currState = <AppStore>showTrack(currState, {
type: SHOW_TRACK,
payload: {timelineId: action.payload.timelineId, tracks: action.payload.tracks[track]}
});
} else {
currState = <AppStore>hideTrack(currState, {
type: HIDE_TRACK,
payload: {timelineId: action.payload.timelineId, tracks: action.payload.tracks[track]}
});
}
}
return currState;
}
/**
* Show track
* 1. change selected status in track
* 2. add track to each subject
*/
function showTrack(state: AppStore, action: ActionWithPayload<any>): Map<string, any> {
return state.withMutations(map => {
// get highest track order
const currentTrackOrder: number = action.payload.tracks.order;
// change track selection order
const tracks: List<ITrack> = map.getIn(['timelines', action.payload.timelineId, 'tracks'])
.map((track: ITrack) => {
if (track.name === action.payload.tracks.track.name) {
return <ITrack>track.set('selected', true).set('order', currentTrackOrder);
} else {
return track;
}
});
map.setIn(['timelines', action.payload.timelineId, 'tracks'], tracks);
// add track to each subject
const possibleSubjects: Map<string, ISubject> = map.getIn(['timelines', action.payload.timelineId, 'subjects']);
const modifiedSubjects: Map<string, ISubject> = <Map<string, ISubject>>possibleSubjects.map((subject: ISubject) => {
const subjectTracks: List<ITrack> = <List<ITrack>> subject.tracks.map((track: ITrack) => {
if (track.name === action.payload.tracks.track.name) {
return new TrackRecord({
name: action.payload.tracks.track.name,
expansionLevel: action.payload.tracks.track.expansionLevel,
selected: true,
order: currentTrackOrder,
data: []
});
} else {
return track;
}
});
return <ISubject>new SubjectRecord({
subjectId: subject.subjectId,
tracks: subjectTracks
});
});
map.setIn(['timelines', action.payload.timelineId, 'subjects'], modifiedSubjects);
});
}
/**
* Hide track
* 1. set selected status to false in track
* 2. remove track from each subject
*/
function hideTrack(state: AppStore, action: ActionWithPayload<any>): Map<string, any> {
return state.withMutations(map => {
const tracks: List<ITrack> = map.getIn(['timelines', action.payload.timelineId, 'tracks'])
.sort((track1, track2) => track1.order - track2.order)
.map((track: ITrack) => {
if (track.get('name') === action.payload.tracks.track.get('name')) {
return track.set('selected', false).set('order', null);
}
return track;
})
.sort(track => track.name);
map.setIn(['timelines', action.payload.timelineId, 'tracks'], tracks);
// remove track from each subject
const possibleSubjects: OrderedMap<string, ISubject> = map.getIn(['timelines', action.payload.timelineId, 'subjects']);
const modifiedSubjects: OrderedMap<string, ISubject> =
<OrderedMap<string, ISubject>>possibleSubjects.map((subject: ISubject) => {
return <ISubject>new SubjectRecord({
subjectId: subject.subjectId,
tracks: subject.tracks.filter((track: ITrack) => {
return track.name !== action.payload.tracks.track.name;
})
});
});
map.setIn(['timelines', action.payload.timelineId, 'subjects'], modifiedSubjects);
});
}
/**
* Update subject track data
*/
function updateData(state: AppStore, action: ActionWithPayload<any>): Map<string, any> {
return state.withMutations(map => {
action.payload.subjects.forEach((subject: ISubject) => {
map.setIn(['timelines', action.payload.timelineId, 'subjects', subject.subjectId],
<ISubject>new SubjectRecord({
subjectId: subject.subjectId,
tracks: subject.tracks
}));
});
});
}
/**
* Change page
* 1. set page limit and offset
* 2. update displayed subjects
*/
function changePage(state: AppStore, action: ActionWithPayload<{timelineId: TimelineId, page: ITimelinePage}>): Map<string, any> {
return state.withMutations(map => {
// set limit and offset
map.setIn(['timelines', action.payload.timelineId, 'page'],
new PageRecord({
limit: action.payload.page.limit,
offset: action.payload.page.offset
}));
// get the new page of subject ids
const displayedSubjects: string[] = [];
const possibleSubjects: Map<string, ISubject> = map.getIn(['timelines', action.payload.timelineId, 'subjects']);
// when we update page we want to make request for all of the current tracks, so we need to mark them as changed,
// so request for them will be performed
const modifiedPossibleSubjects: Map<string, ISubject> = <Map<string, ISubject>>possibleSubjects.map((subject: ISubject) => {
return <ISubject>new SubjectRecord({
subjectId: subject.subjectId,
tracks: subject.tracks.map((track: ITrack) => track.set('changed', true))
});
});
const slicedPossibleSubjects: Iterable<string, ISubject> = modifiedPossibleSubjects
.slice(action.payload.page.offset, (action.payload.page.limit + action.payload.page.offset));
slicedPossibleSubjects.forEach((subject: ISubject, subjectId: string) => {
displayedSubjects.push(subjectId);
});
// update subjects: track's `changed` property is set to true
map.setIn(['timelines', action.payload.timelineId, 'subjects'], modifiedPossibleSubjects);
// update displayed subjects
map.setIn(['timelines', action.payload.timelineId, 'displayedSubjects'], List(displayedSubjects));
});
}
/**
* Update zoom
* 1. update zoom to min and max values of all tracks, if action zoom payload is undefined
* 2. update zoom using action zoom payload if it is not undefined
*/
function updateZoom(state: AppStore, action: ActionWithPayload<{timelineId: TimelineId, zoom: any}>): Map<string, any> {
if (action.payload.zoom) {
return state.setIn(['timelines', action.payload.timelineId, 'zoom'], action.payload.zoom);
} else {
const possibleSubjects: OrderedMap<string, ISubject> = state.getIn(['timelines', action.payload.timelineId, 'subjects']);
const zoomRange: any = possibleSubjects.reduce((range: any, subject: ISubject) => {
subject.tracks.forEach((track: ITrack) => {
if (track.data) {
track.data.forEach((dataPoint: ITrackDataPoint) => {
const startDayHour = dataPoint.start.dayHour;
if (startDayHour > range.absMax) {
range.absMax = startDayHour;
}
if (startDayHour < range.absMin) {
range.absMin = startDayHour;
}
if (dataPoint.end) {
const endDayHour = dataPoint.end.dayHour;
if (endDayHour > range.absMax) {
range.absMax = endDayHour;
}
}
});
}
});
return range;
}, {absMax: 0, absMin: 0});
const diff = TrackUtils.getDiff(zoomRange.absMin, zoomRange.absMax, 0.05);
zoomRange.absMin -= diff;
zoomRange.absMax += diff;
if (zoomRange.absMax !== zoomRange.absMin) {
const existingZoom: IZoom = state.getIn(['timelines', action.payload.timelineId, 'zoom']);
if (existingZoom !== null && existingZoom.zoomed) {
zoomRange.zoomMin = existingZoom.zoomMin;
zoomRange.zoomMax = existingZoom.zoomMax;
zoomRange.zoomed = existingZoom.zoomed;
} else {
if (zoomRange.absMax - diff > 0) {
// Min is set to zero if max is positive to avoid start day to be large negative date
zoomRange.zoomMin = 0;
zoomRange.zoomMax = zoomRange.absMax;
} else {
// Min is set to its value when max is not positive to avoid having only diff-width interval by default
zoomRange.zoomMin = zoomRange.absMin;
zoomRange.zoomMax = zoomRange.absMax;
}
}
return state.setIn(['timelines', action.payload.timelineId, 'zoom'], <IZoom>new ZoomRecord(zoomRange));
} else {
return state;
}
}
}
/**
* expand or collapse track
* 1. if payload's subject id is null, then expand/collapse track for all subject
* 2. if payload's subject id is not null, then expand/collapse track for only that subject
*/
function expandOrCollapseTrack(state: AppStore, action: ActionWithPayload<any>): Map<string, any> {
return state.withMutations(map => {
const subjectIds: string[] = [];
const possibleSubjects: OrderedMap<string, ISubject> = map.getIn(['timelines', action.payload.timelineId, 'subjects']);
if (action.payload.expansion.subjectId) {
subjectIds.push(action.payload.expansion.subjectId);
} else {
possibleSubjects.forEach((subject: ISubject, subjectId: string) => {
subjectIds.push(subjectId);
});
}
const modifiedSubjects: Map<string, ISubject> = <Map<string, ISubject>>possibleSubjects.map((subject: ISubject) => {
if (subjectIds.indexOf(subject.subjectId) >= 0) {
return <ISubject>new SubjectRecord({
subjectId: subject.subjectId,
tracks: subject.tracks.map((track: ITrack) => {
if (track.name === action.payload.expansion.track &&
track.expansionLevel === action.payload.expansion.expansionLevel) {
return <ITrack>new TrackRecord({
name: track.name,
expansionLevel: action.payload.expansion.expand ? track.expansionLevel + 1 : track.expansionLevel - 1,
selected: true,
order: track.order
});
} else {
return track.set('changed', false);
}
})
});
} else {
return subject;
}
});
map.setIn(['timelines', action.payload.timelineId, 'subjects'], modifiedSubjects);
});
}
function changeDayZeroOptions(state: AppStore,
action: ActionWithPayload<{timelineId: TimelineId, dayZeroOptions: DayZero[]}>): Map<string, any> {
return state.setIn(['timelines', action.payload.timelineId, 'dayZeroOptions'], action.payload.dayZeroOptions);
}
// Change the settings for labs yaxis value
function changeLabsYAxisValue(state: AppStore,
action: ActionWithPayload<{timelineId: TimelineId, labsYAxisValue: any}>): Map<string, any> {
return state.setIn(['timelines', action.payload.timelineId, 'labsYAxisValue'], action.payload.labsYAxisValue);
}
// Change the settings for labs yaxis value
function changeSpirometryYAxisValue(state: AppStore,
action: ActionWithPayload<{timelineId: TimelineId, spirometryYAxisValue: any}>): Map<string, any> {
return state.setIn(['timelines', action.payload.timelineId, 'spirometryYAxisValue'], action.payload.spirometryYAxisValue);
}
// Change the settings for ecg yaxis value
function changeEcgYAxisValue(state: AppStore, action: ActionWithPayload<{timelineId: TimelineId, ecgYAxisValue: any}>)
: Map<string, any> {
return state.setIn(['timelines', action.payload.timelineId, 'ecgYAxisValue'], action.payload.ecgYAxisValue);
}
// Change the settings for ecg warnings
function changeEcgWarnings(state: AppStore, action: ActionWithPayload<{timelineId: TimelineId, ecgWarnings: any}>)
: Map<string, any> {
return state.setIn(['timelines', action.payload.timelineId, 'ecgWarnings'], action.payload.ecgWarnings);
}
// Change the settings for vitals yaxis value
function changeVitalsYAxisValue(state: AppStore, action: ActionWithPayload<{timelineId: TimelineId, vitalsYAxisValue: any}>)
: Map<string, any> {
return state.setIn(['timelines', action.payload.timelineId, 'vitalsYAxisValue'], action.payload.vitalsYAxisValue);
}
// Change the dayZero
function changeDayZero(state: AppStore, action: ActionWithPayload<{timelineId: TimelineId, dayZero: any}>): Map<string, any> {
return state.setIn(['timelines', action.payload.timelineId, 'dayZero'], action.payload.dayZero);
}
// Change the jump t timeline flag
function changePerformedJumpFlag(state: AppStore,
action: ActionWithPayload<{timelineId: TimelineId, performedJumpToTimeline: boolean}>)
: Map<string, any> {
return state.setIn(['timelines', action.payload.timelineId, 'performedJumpToTimeline'], action.payload.performedJumpToTimeline);
}
// Add plot bands to timeline tracks
function updatePlotBands(state: AppStore, action: ActionWithPayload<{timelineId: TimelineId, eventData: any}>): Map<string, any> {
return state.withMutations(map => {
const {point, ctrlKey} = action.payload.eventData;
const {low: from, high: to} = point;
const plotBand: IHighlightedPlotArea = {from, to};
let plotBands: IHighlightedPlotArea[] = map.getIn(['timelines', action.payload.timelineId, 'plotBands']).toJS();
const isValuePresent: boolean = plotBands.some(area => isEqual(area, plotBand));
// Check if ctrl was pressed
if (ctrlKey) {
// Check if new value is already there
if (isValuePresent) {
// Remove existing value from array
plotBands = plotBands.filter(area => !isEqual(area, plotBand));
} else {
// Add new value to the array
plotBands.push(plotBand);
}
} else {
// Clear the array no matter what
plotBands.length = 0;
// If new value is not present yet then add it
if (!isValuePresent) {
plotBands.push(plotBand);
}
}
map.setIn(['timelines', action.payload.timelineId, 'plotBands'], fromJS(plotBands));
});
}
// Change the initialization flag
function changedIsInitialized(state: AppStore, action: ActionWithPayload<InitializedTimeline>): Map<string, any> {
if (action.payload.timelineId) {
return state.setIn(['timelines', action.payload.timelineId, 'isInitialized'], action.payload.isInitialized);
} else {
return state.withMutations(mutatedState => {
const timelines = mutatedState.get('timelines').keySeq();
timelines.forEach((timeline) => {
return mutatedState.setIn(['timelines', timeline, 'isInitialized'], action.payload.isInitialized);
});
});
}
}
}