Skip to content

Commit

Permalink
Merge pull request #1159 from JGreenlee/timelinecontext-fixes
Browse files Browse the repository at this point in the history
TimelineContext fixes
  • Loading branch information
shankari authored May 21, 2024
2 parents a16bbc0 + 39d463c commit 34238f6
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 45 deletions.
8 changes: 7 additions & 1 deletion www/js/Main.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* Once onboarding is done, this is the main app content.
Includes the bottom navigation bar and each of the tabs. */

import React from 'react';
import React, { useEffect } from 'react';
import { useContext, useMemo, useState } from 'react';
import { BottomNavigation, useTheme } from 'react-native-paper';
import { AppContext } from './App';
Expand Down Expand Up @@ -55,6 +55,12 @@ const Main = () => {
return showMetrics ? defaultRoutes(t) : defaultRoutes(t).filter((r) => r.key != 'metrics');
}, [appConfig, t]);

useEffect(() => {
const { setShouldUpdateTimeline } = timelineContext;
// update TimelineScrollList component only when the active tab is 'label' to fix leaflet map issue
setShouldUpdateTimeline(!index);
}, [index]);

return (
<TimelineContext.Provider value={timelineContext}>
<BottomNavigation
Expand Down
20 changes: 7 additions & 13 deletions www/js/TimelineContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import {
} from './diary/timelineHelper';
import { getPipelineRangeTs } from './services/commHelper';
import { getNotDeletedCandidates, mapInputsToTimelineEntries } from './survey/inputMatcher';
import { publish } from './customEventHandler';
import { EnketoUserInputEntry } from './survey/enketo/enketoHelper';
import { VehicleIdentity } from './types/appConfigTypes';
import { primarySectionForTrip } from './diary/diaryHelper';
Expand Down Expand Up @@ -50,6 +49,8 @@ type ContextProps = {
loadMoreDays: (when: 'past' | 'future', nDays: number) => void;
loadSpecificWeek: (d: string) => void;
refreshTimeline: () => void;
shouldUpdateTimeline: Boolean;
setShouldUpdateTimeline: React.Dispatch<React.SetStateAction<boolean>>;
};

export const useTimelineContext = (): ContextProps => {
Expand All @@ -69,6 +70,9 @@ export const useTimelineContext = (): ContextProps => {
const [timelineLabelMap, setTimelineLabelMap] = useState<TimelineLabelMap | null>(null);
const [timelineNotesMap, setTimelineNotesMap] = useState<TimelineNotesMap | null>(null);
const [refreshTime, setRefreshTime] = useState<Date | null>(null);
// Leaflet map encounters an error when prerendered, so we need to render the TimelineScrollList component when the active tab is 'label'
// 'shouldUpdateTimeline' gets updated based on the current tab index, and we can use it to determine whether to render the timeline or not
const [shouldUpdateTimeline, setShouldUpdateTimeline] = useState(true);

// initialization, once the appConfig is loaded
useEffect(() => {
Expand Down Expand Up @@ -135,10 +139,6 @@ export const useTimelineContext = (): ContextProps => {
);
setTimelineLabelMap(newTimelineLabelMap);
setTimelineNotesMap(newTimelineNotesMap);
publish('applyLabelTabFilters', {
timelineMap,
timelineLabelMap: newTimelineLabelMap,
});
setTimelineIsLoading(false);
}, [timelineMap]);

Expand Down Expand Up @@ -316,14 +316,6 @@ export const useTimelineContext = (): ContextProps => {
},
};
setTimelineLabelMap(newTimelineLabelMap);
setTimeout(
() =>
publish('applyLabelTabFilters', {
timelineMap,
timelineLabelMap: newTimelineLabelMap,
}),
30000,
); // wait 30s before reapplying filters
} else if (inputType == 'note') {
const notesForEntry = timelineNotesMap?.[oid] || [];
const newAddition = { data: userInput, metadata: { write_ts: nowTs } };
Expand Down Expand Up @@ -356,6 +348,8 @@ export const useTimelineContext = (): ContextProps => {
notesFor,
confirmedModeFor,
addUserInputToEntry,
shouldUpdateTimeline,
setShouldUpdateTimeline,
};
};

Expand Down
56 changes: 29 additions & 27 deletions www/js/diary/LabelTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,9 @@ import { fillLocationNamesOfTrip } from './addressNamesHelper';
import { logDebug } from '../plugin/logger';
import { configuredFilters as multilabelConfiguredFilters } from '../survey/multilabel/infinite_scroll_filters';
import { configuredFilters as enketoConfiguredFilters } from '../survey/enketo/infinite_scroll_filters';
import { TimelineEntry } from '../types/diaryTypes';
import TimelineContext, { LabelTabFilter, TimelineLabelMap } from '../TimelineContext';
import { TimelineEntry, isTrip } from '../types/diaryTypes';
import TimelineContext, { LabelTabFilter } from '../TimelineContext';
import { AppContext } from '../App';
import { subscribe } from '../customEventHandler';

type LabelContextProps = {
displayedEntries: TimelineEntry[] | null;
Expand All @@ -29,8 +28,9 @@ export const LabelTabContext = createContext<LabelContextProps>({} as LabelConte

const LabelTab = () => {
const { appConfig } = useContext(AppContext);
const { pipelineRange, timelineMap } = useContext(TimelineContext);
const { pipelineRange, timelineMap, timelineLabelMap } = useContext(TimelineContext);

const [filterRefreshTs, setFilterRefreshTs] = useState<number>(0); // used to force a refresh of the filters
const [filterInputs, setFilterInputs] = useState<LabelTabFilter[]>([]);
const [displayedEntries, setDisplayedEntries] = useState<TimelineEntry[] | null>(null);

Expand All @@ -43,46 +43,48 @@ const LabelTab = () => {
appConfig.survey_info?.['trip-labels'] == 'ENKETO'
? enketoConfiguredFilters
: multilabelConfiguredFilters;
const allFalseFilters = tripFilters.map((f, i) => ({
const filtersWithState = tripFilters.map((f, i) => ({
...f,
state: i == 0 ? true : false, // only the first filter will have state true on init
}));
setFilterInputs(allFalseFilters);
setFilterInputs(filtersWithState);
}

subscribe('applyLabelTabFilters', (e) => {
logDebug('applyLabelTabFilters event received, calling applyFilters');
applyFilters(e.detail.timelineMap, e.detail.timelineLabelMap || {});
});
}, [appConfig]);

useEffect(() => {
if (!timelineMap) return;
const tripsRead = Object.values(timelineMap || {});
tripsRead
.slice()
.reverse()
.forEach((trip) => fillLocationNamesOfTrip(trip));
const timelineEntries = Array.from(timelineMap.values());
if (!timelineEntries?.length) return;
timelineEntries.reverse().forEach((entry) => {
if (isTrip(entry)) fillLocationNamesOfTrip(entry);
});
}, [timelineMap]);

function applyFilters(timelineMap, labelMap: TimelineLabelMap) {
useEffect(() => {
if (!timelineMap || !timelineLabelMap || !filterInputs.length) return;
logDebug('Applying filters');
const allEntries: TimelineEntry[] = Array.from(timelineMap.values());
const activeFilter = filterInputs?.find((f) => f.state == true);
let entriesToDisplay = allEntries;
if (activeFilter) {
const cutoffTs = new Date().getTime() / 1000 - 30; // 30s ago, as epoch seconds
const nowTs = new Date().getTime() / 1000;
const entriesAfterFilter = allEntries.filter((e) => {
// if the entry has a recently recorded user input, it is immune to filtering
const labels = labelMap[e._id.$oid];
for (let labelValue of Object.values(labels || [])) {
logDebug(`LabelTab filtering: labelValue = ${JSON.stringify(labelValue)}`);
if (labelValue?.metadata?.write_ts || 0 > cutoffTs) {
logDebug('LabelTab filtering: entry has recent user input, keeping');
return true;
}
const labels = timelineLabelMap[e._id.$oid];
const mostRecentInputTs = Object.values(labels || []).reduce((acc, label) => {
if (label?.metadata?.write_ts && label.metadata.write_ts > acc)
return label.metadata.write_ts;
return acc;
}, 0);
const entryImmuneUntil = mostRecentInputTs + 30; // 30s after the most recent user input
if (entryImmuneUntil > nowTs) {
logDebug(`LabelTab filtering: entry still immune, skipping.
Re-applying filters at ${entryImmuneUntil}`);
setTimeout(() => setFilterRefreshTs(entryImmuneUntil), (entryImmuneUntil - nowTs) * 1000);
return true;
}
// otherwise, just apply the filter
return activeFilter?.filter(e, labelMap[e._id.$oid]);
return activeFilter?.filter(e, timelineLabelMap[e._id.$oid]);
});
/* next, filter out any untracked time if the trips that came before and
after it are no longer displayed */
Expand All @@ -100,7 +102,7 @@ const LabelTab = () => {
logDebug('No active filter, displaying all entries');
}
setDisplayedEntries(entriesToDisplay);
}
}, [timelineMap, filterInputs, timelineLabelMap, filterRefreshTs]);

// once pipelineRange is set, update all unprocessed inputs
useEffect(() => {
Expand Down
3 changes: 2 additions & 1 deletion www/js/diary/addressNamesHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export function useLocalStorage<T>(key: string, initialValue: T) {

import Bottleneck from 'bottleneck';
import { displayError, logDebug } from '../plugin/logger';
import { CompositeTrip } from '../types/diaryTypes';

let nominatimLimiter = new Bottleneck({ maxConcurrent: 2, minTime: 500 });
export const resetNominatimLimiter = () => {
Expand Down Expand Up @@ -137,7 +138,7 @@ async function fetchNominatimLocName(loc_geojson) {
}

// Schedules nominatim fetches for the start and end locations of a trip
export function fillLocationNamesOfTrip(trip) {
export function fillLocationNamesOfTrip(trip: CompositeTrip) {
nominatimLimiter.schedule(() => fetchNominatimLocName(trip.end_loc));
nominatimLimiter.schedule(() => fetchNominatimLocName(trip.start_loc));
}
Expand Down
11 changes: 8 additions & 3 deletions www/js/diary/list/LabelListScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,13 @@ import { displayErrorMsg } from '../../plugin/logger';

const LabelListScreen = () => {
const { filterInputs, setFilterInputs, displayedEntries } = useContext(LabelTabContext);
const { timelineMap, loadSpecificWeek, timelineIsLoading, refreshTimeline } =
useContext(TimelineContext);
const {
timelineMap,
loadSpecificWeek,
timelineIsLoading,
refreshTimeline,
shouldUpdateTimeline,
} = useContext(TimelineContext);
const { colors } = useTheme();

return (
Expand Down Expand Up @@ -42,7 +47,7 @@ const LabelListScreen = () => {
/>
</NavBar>
<View style={{ flex: 1, backgroundColor: colors.background }}>
<TimelineScrollList listEntries={displayedEntries} />
{shouldUpdateTimeline && <TimelineScrollList listEntries={displayedEntries} />}
</View>
</>
);
Expand Down
2 changes: 2 additions & 0 deletions www/js/diary/useDerivedProperties.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
getLocalTimeString,
getDetectedModes,
isMultiDay,
primarySectionForTrip,
} from './diaryHelper';
import TimelineContext from '../TimelineContext';

Expand All @@ -24,6 +25,7 @@ const useDerivedProperties = (tlEntry) => {

return {
confirmedMode: confirmedModeFor(tlEntry),
primary_ble_sensed_mode: primarySectionForTrip(tlEntry)?.ble_sensed_mode?.baseMode,
displayDate: getFormattedDate(beginFmt, endFmt),
displayStartTime: getLocalTimeString(beginDt),
displayEndTime: getLocalTimeString(endDt),
Expand Down

0 comments on commit 34238f6

Please sign in to comment.