Creating a mechanism to store settings both in the database and in redux.

This commit is contained in:
Eric van der Vlist 2022-10-03 21:08:27 +02:00
parent 07edc22d31
commit c0015a7cfa
10 changed files with 272 additions and 18 deletions

View File

@ -25,10 +25,9 @@ import {
} from '../../lib/background-geolocation'; } from '../../lib/background-geolocation';
import { appendTrkpt, deleteCurrent, saveCurrent } from '../../db/gpx'; import { appendTrkpt, deleteCurrent, saveCurrent } from '../../db/gpx';
import { enterAnimation, leaveAnimation } from '../../lib/animation'; import { enterAnimation, leaveAnimation } from '../../lib/animation';
import { useDispatch } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { mapActions } from '../../store/map'; import { mapActions } from '../../store/map';
import { SettingsState } from '../../store/settings';
const MIN_TIME_BETWEEN_LOCATIONS = 15 * 1000;
declare global { declare global {
var $lastValidLocationTime: number; var $lastValidLocationTime: number;
@ -41,6 +40,10 @@ const GpxRecord: React.FC<{}> = () => {
const [watcher_id, setWatcher_id] = useState(); const [watcher_id, setWatcher_id] = useState();
const geolocationsSettingsState = useSelector(
(state: { settings: SettingsState }) => state.settings.geolocation
);
const gpxes = useFind({ const gpxes = useFind({
selector: { selector: {
type: 'gpx', type: 'gpx',
@ -72,7 +75,7 @@ const GpxRecord: React.FC<{}> = () => {
); );
if ( if (
location.time - globalThis.$lastValidLocationTime > location.time - globalThis.$lastValidLocationTime >
MIN_TIME_BETWEEN_LOCATIONS geolocationsSettingsState.minTimeInterval
) { ) {
globalThis.$lastValidLocationTime = location.time; globalThis.$lastValidLocationTime = location.time;
appendTrkpt(db, { appendTrkpt(db, {
@ -98,7 +101,10 @@ const GpxRecord: React.FC<{}> = () => {
const startRecording = () => { const startRecording = () => {
globalThis.$lastValidLocationTime = 0; globalThis.$lastValidLocationTime = 0;
startBackgroundGeolocation(newLocationHandler).then((result) => { startBackgroundGeolocation(
newLocationHandler,
geolocationsSettingsState.minDistance
).then((result) => {
setWatcher_id(result); setWatcher_id(result);
}); });
setIsRecording(true); setIsRecording(true);

View File

@ -0,0 +1,177 @@
import React, { Fragment, useEffect, useRef, useState } from 'react';
import { useDB } from 'react-pouchdb';
import {
IonButton,
IonButtons,
IonContent,
IonIcon,
IonInput,
IonItem,
IonLabel,
IonList,
IonListHeader,
IonModal,
IonTitle,
IonToolbar,
} from '@ionic/react';
import { options, text } from 'ionicons/icons';
import { enterAnimation, leaveAnimation } from '../../lib/animation';
import { useDispatch, useSelector } from 'react-redux';
import { settingsActions, SettingsState } from '../../store/settings';
import uri from '../../lib/ids';
import _ from 'lodash';
const Settings: React.FC<{}> = () => {
const dispatch = useDispatch();
const db = useDB();
useEffect(() => {
const initFromDb = async () => {
try {
const settingsFromDb = await db.get(uri('settings', {}));
console.log(
`settingsFromDb: ${JSON.stringify(settingsFromDb.settings)}`
);
dispatch(settingsActions.saveSettings(settingsFromDb.settings));
} catch {}
};
initFromDb();
}, []);
const settingsState = useSelector(
(state: { settings: SettingsState }) => state.settings
);
useEffect(() => {
if (!settingsState.default) {
const settingsFromRedux = async () => {
const id = uri('settings', {});
var settingsFromDb;
try {
settingsFromDb = await db.get(id);
} catch (error) {
console.log(
`Error getting settings from db: ${JSON.stringify(error)}`
);
settingsFromDb = { _id: id, settings: {} };
}
if (!_.isEqual(settingsState, settingsFromDb.settings)) {
console.log(`settingsFromRedux: ${JSON.stringify(settingsState)}`);
settingsFromDb.settings = settingsState;
db.put(settingsFromDb);
}
};
settingsFromRedux();
}
}, [settingsState]);
const pseudo = useRef<HTMLIonInputElement>(null);
const minTimeInterval = useRef<HTMLIonInputElement>(null);
const minDistance = useRef<HTMLIonInputElement>(null);
const modal = useRef<HTMLIonModalElement>(null);
const dismiss = () => {
modal.current?.dismiss();
};
const save = () => {
dispatch(
settingsActions.saveSettings({
user: {
pseudo: pseudo.current?.value,
},
geolocation: {
minTimeInterval: minTimeInterval.current?.value,
minDistance: minDistance.current?.value,
},
})
);
dismiss();
};
return (
<Fragment>
<IonButton id='open-SettingsDialog'>
<IonIcon slot='icon-only' icon={options} />
</IonButton>
<IonModal
ref={modal}
trigger='open-SettingsDialog'
enterAnimation={enterAnimation}
leaveAnimation={leaveAnimation}
>
<IonToolbar>
<IonTitle>Settings</IonTitle>
<IonButtons slot='end'>
<IonButton onClick={() => dismiss()}>Close</IonButton>
</IonButtons>
</IonToolbar>
<IonContent>
<IonList lines='full' class='ion-no-margin'>
<IonListHeader lines='full'>
<IonLabel>User</IonLabel>
</IonListHeader>
<IonItem>
<IonLabel>Pseudo: </IonLabel>
<IonInput
ref={pseudo}
value={settingsState.user.pseudo}
autocomplete='nickname'
></IonInput>
</IonItem>
</IonList>
<IonList lines='full' class='ion-no-margin'>
<IonListHeader lines='full'>
<IonLabel>Geolocation</IonLabel>
</IonListHeader>
<IonItem>
<IonLabel>Minimal time interval (ms): </IonLabel>
<IonInput
value={settingsState.geolocation.minTimeInterval}
ref={minTimeInterval}
inputMode='numeric'
type='number'
></IonInput>
</IonItem>
<IonItem>
<IonLabel>Minimal distance (m): </IonLabel>
<IonInput
value={settingsState.geolocation.minDistance}
ref={minDistance}
inputMode='numeric'
type='number'
></IonInput>
</IonItem>
</IonList>
<IonItem>
<IonToolbar class='secondary'>
<IonButtons slot='secondary'>
<IonButton
shape='round'
fill='solid'
color='primary'
onClick={() => save()}
>
Save
</IonButton>
<IonButton
shape='round'
fill='outline'
color='primary'
onClick={() => dismiss()}
>
Cancel
</IonButton>
</IonButtons>
</IonToolbar>
</IonItem>
</IonContent>
</IonModal>
</Fragment>
);
};
export default Settings;

View File

@ -25,6 +25,7 @@ import { initDb } from '../../db';
import TileServerChooserButton from './TileServerChooserButton'; import TileServerChooserButton from './TileServerChooserButton';
import TileServerChooserDialog from './TileServerChooserDialog'; import TileServerChooserDialog from './TileServerChooserDialog';
import TrackBrowser from './TracksBrowser'; import TrackBrowser from './TracksBrowser';
import Settings from './Settings';
const Map: react.FC<{}> = (props: {}) => { const Map: react.FC<{}> = (props: {}) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -68,6 +69,7 @@ const Map: react.FC<{}> = (props: {}) => {
<TrackBrowser/> <TrackBrowser/>
<GpxRecord /> <GpxRecord />
<TileServerChooserButton /> <TileServerChooserButton />
<Settings />
</IonButtons> </IonButtons>
</IonToolbar> </IonToolbar>
</IonHeader> </IonHeader>

View File

@ -1,17 +1,22 @@
import _ from 'lodash'; import _, { constant } from 'lodash';
import uri from '../lib/ids';
const DBDEFINITION = '--db-definition--'; const dbDefinitionId = uri('dbdef', {});
const dbDefinition = { const currentDbDefinition = {
_id: DBDEFINITION, _id: dbDefinitionId,
type: DBDEFINITION, type: dbDefinitionId,
version: '0.000001', def: { version: '0.000001' },
}; };
export const initDb = async (db: any, setDbReady: any) => { export const initDb = async (db: any, setDbReady: any) => {
var previousDbDefinition = {
_id: dbDefinitionId,
type: dbDefinitionId,
def: { version: '0' },
};
try { try {
await db.get(DBDEFINITION); previousDbDefinition = await db.get(dbDefinitionId);
// TODO: support migrations
} catch (error: any) { } catch (error: any) {
if (error.status !== 404) { if (error.status !== 404) {
console.log( console.log(
@ -21,6 +26,11 @@ export const initDb = async (db: any, setDbReady: any) => {
} }
} }
if (previousDbDefinition.def.version < currentDbDefinition.def.version) {
previousDbDefinition.def = currentDbDefinition.def;
db.put(previousDbDefinition);
// TODO: support migrations
}
await await db.compact(); await await db.compact();
await db.viewCleanup(); await db.viewCleanup();

View File

@ -34,7 +34,11 @@ const backgroundGeolocationConfig = {
distanceFilter: 10, distanceFilter: 10,
}; };
export const startBackgroundGeolocation = async (newLocationHandler: any) => { export const startBackgroundGeolocation = async (
newLocationHandler: any,
distanceFilter: number
) => {
backgroundGeolocationConfig.distanceFilter = distanceFilter;
const locationHandler = (location: any, error: any) => { const locationHandler = (location: any, error: any) => {
console.log('com.dyomedea.dyomedea LOG', ' - Callback'); console.log('com.dyomedea.dyomedea LOG', ' - Callback');
if (error) { if (error) {

View File

@ -103,3 +103,14 @@ describe('Checking trkpt ids', () => {
}); });
}); });
}); });
describe('Checking settings id', () => {
test(', vice', () => {
const rte = uri('settings', {});
expect(rte).toBe('settings');
});
test(', and versa', () => {
const rte = uri('settings', 'settings');
expect(rte).toMatchObject({});
});
});

View File

@ -1,7 +1,8 @@
import { route } from 'docuri'; import { route } from 'docuri';
const routes = { const routes = {
config: route('config'), dbdef: route('dbdef'),
settings: route('settings'),
gpx: route('gpx/:gpx'), gpx: route('gpx/:gpx'),
trk: route('gpx/:gpx/trk/:trk'), trk: route('gpx/:gpx/trk/:trk'),
trkseg: route('gpx/:gpx/trk/:trk/:trkseg'), trkseg: route('gpx/:gpx/trk/:trk/:trkseg'),

View File

@ -1,9 +1,10 @@
import { configureStore } from '@reduxjs/toolkit'; import { configureStore } from '@reduxjs/toolkit';
import mapReducer from './map'; import mapReducer from './map';
import settingsReducer from './settings';
const store = configureStore({ const store = configureStore({
reducer: { map: mapReducer }, reducer: { map: mapReducer, settings: settingsReducer },
}); });
export default store; export default store;

37
src/store/settings.ts Normal file
View File

@ -0,0 +1,37 @@
import { createSlice } from '@reduxjs/toolkit';
export interface SettingsState {
default?: boolean;
user: {
pseudo: string;
};
geolocation: {
minTimeInterval: number;
minDistance: number;
};
}
export const initialSettingsState: SettingsState = {
default: true,
user: {
pseudo: 'user',
},
geolocation: {
minTimeInterval: 15000,
minDistance: 10,
},
};
const settingsSlice = createSlice({
name: 'settings',
initialState: initialSettingsState,
reducers: {
saveSettings: (state, action) => {
return action.payload;
},
},
});
export const settingsActions = settingsSlice.actions;
export default settingsSlice.reducer;

View File

@ -64,6 +64,11 @@ ion-modal ion-toolbar {
--color: white; --color: white;
} }
ion-modal ion-toolbar.secondary {
--background: inherit;
--color: inherit;
}
ion-modal ion-content { ion-modal ion-content {
background-color: rgba(255,255,255,.7) background-color: rgba(255,255,255,.7)
; ;