Creating a mechanism to store settings both in the database and in redux.
This commit is contained in:
parent
07edc22d31
commit
c0015a7cfa
|
@ -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);
|
||||||
|
|
|
@ -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;
|
|
@ -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>
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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({});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -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'),
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
|
@ -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)
|
||||||
;
|
;
|
||||||
|
|
Loading…
Reference in New Issue