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';
|
||||
import { appendTrkpt, deleteCurrent, saveCurrent } from '../../db/gpx';
|
||||
import { enterAnimation, leaveAnimation } from '../../lib/animation';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { mapActions } from '../../store/map';
|
||||
|
||||
const MIN_TIME_BETWEEN_LOCATIONS = 15 * 1000;
|
||||
import { SettingsState } from '../../store/settings';
|
||||
|
||||
declare global {
|
||||
var $lastValidLocationTime: number;
|
||||
|
@ -41,6 +40,10 @@ const GpxRecord: React.FC<{}> = () => {
|
|||
|
||||
const [watcher_id, setWatcher_id] = useState();
|
||||
|
||||
const geolocationsSettingsState = useSelector(
|
||||
(state: { settings: SettingsState }) => state.settings.geolocation
|
||||
);
|
||||
|
||||
const gpxes = useFind({
|
||||
selector: {
|
||||
type: 'gpx',
|
||||
|
@ -72,7 +75,7 @@ const GpxRecord: React.FC<{}> = () => {
|
|||
);
|
||||
if (
|
||||
location.time - globalThis.$lastValidLocationTime >
|
||||
MIN_TIME_BETWEEN_LOCATIONS
|
||||
geolocationsSettingsState.minTimeInterval
|
||||
) {
|
||||
globalThis.$lastValidLocationTime = location.time;
|
||||
appendTrkpt(db, {
|
||||
|
@ -98,7 +101,10 @@ const GpxRecord: React.FC<{}> = () => {
|
|||
|
||||
const startRecording = () => {
|
||||
globalThis.$lastValidLocationTime = 0;
|
||||
startBackgroundGeolocation(newLocationHandler).then((result) => {
|
||||
startBackgroundGeolocation(
|
||||
newLocationHandler,
|
||||
geolocationsSettingsState.minDistance
|
||||
).then((result) => {
|
||||
setWatcher_id(result);
|
||||
});
|
||||
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 TileServerChooserDialog from './TileServerChooserDialog';
|
||||
import TrackBrowser from './TracksBrowser';
|
||||
import Settings from './Settings';
|
||||
|
||||
const Map: react.FC<{}> = (props: {}) => {
|
||||
const dispatch = useDispatch();
|
||||
|
@ -68,6 +69,7 @@ const Map: react.FC<{}> = (props: {}) => {
|
|||
<TrackBrowser/>
|
||||
<GpxRecord />
|
||||
<TileServerChooserButton />
|
||||
<Settings />
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</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 = {
|
||||
_id: DBDEFINITION,
|
||||
type: DBDEFINITION,
|
||||
version: '0.000001',
|
||||
const currentDbDefinition = {
|
||||
_id: dbDefinitionId,
|
||||
type: dbDefinitionId,
|
||||
def: { version: '0.000001' },
|
||||
};
|
||||
|
||||
export const initDb = async (db: any, setDbReady: any) => {
|
||||
var previousDbDefinition = {
|
||||
_id: dbDefinitionId,
|
||||
type: dbDefinitionId,
|
||||
def: { version: '0' },
|
||||
};
|
||||
try {
|
||||
await db.get(DBDEFINITION);
|
||||
// TODO: support migrations
|
||||
previousDbDefinition = await db.get(dbDefinitionId);
|
||||
} catch (error: any) {
|
||||
if (error.status !== 404) {
|
||||
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 db.viewCleanup();
|
||||
|
||||
|
|
|
@ -34,7 +34,11 @@ const backgroundGeolocationConfig = {
|
|||
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) => {
|
||||
console.log('com.dyomedea.dyomedea LOG', ' - Callback');
|
||||
if (error) {
|
||||
|
@ -57,8 +61,8 @@ export const startBackgroundGeolocation = async (newLocationHandler: any) => {
|
|||
}
|
||||
console.log(location);
|
||||
if (location !== undefined) {
|
||||
newLocationHandler(location);
|
||||
}
|
||||
newLocationHandler(location);
|
||||
}
|
||||
|
||||
return console.log('com.dyomedea.dyomedea LOG', ' - location: ', location);
|
||||
};
|
||||
|
|
|
@ -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';
|
||||
|
||||
const routes = {
|
||||
config: route('config'),
|
||||
dbdef: route('dbdef'),
|
||||
settings: route('settings'),
|
||||
gpx: route('gpx/:gpx'),
|
||||
trk: route('gpx/:gpx/trk/:trk'),
|
||||
trkseg: route('gpx/:gpx/trk/:trk/:trkseg'),
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { configureStore } from '@reduxjs/toolkit';
|
||||
|
||||
import mapReducer from './map';
|
||||
import settingsReducer from './settings';
|
||||
|
||||
const store = configureStore({
|
||||
reducer: { map: mapReducer },
|
||||
reducer: { map: mapReducer, settings: settingsReducer },
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
ion-modal ion-toolbar.secondary {
|
||||
--background: inherit;
|
||||
--color: inherit;
|
||||
}
|
||||
|
||||
ion-modal ion-content {
|
||||
background-color: rgba(255,255,255,.7)
|
||||
;
|
||||
|
|
Loading…
Reference in New Issue