Sync nomenclatures with fieldnotes server

This commit is contained in:
wpetit 2019-03-12 17:19:11 +01:00
parent 5d5277d7e4
commit f2a49c0936
15 changed files with 111 additions and 44 deletions

View File

@ -56,6 +56,12 @@ class SettingScreen extends React.Component {
name="useNotebookGPS"
defaultValue={settings.useNotebookGPS}
form={form} />
<TextField
label="URL de récupération des nomenclatures"
name="nomenclaturesURL"
defaultValue={ settings.nomenclaturesURL }
form={form}
/>
</Form>
<Text />
<Button full success onPress={form.onSubmit}>

View File

@ -1,30 +1,5 @@
import React from 'react';
import {
View, Text,
List, ListItem,
Left, Right,
Button, Icon,
} from 'native-base';
import { ScrollView } from 'react-native';
import {
addNotification,
NOTIFICATION_LEVEL_WARN,
} from '../../store';
const styles = {
root: {
height: '100%',
},
positionsList: {
height: '100%',
paddingBottom: 100
},
positionsHeader: {
fontWeight: 'bold',
paddingTop: 5,
paddingLeft: 10
}
};
export default class BaseCollect extends React.Component {

View File

@ -33,6 +33,7 @@ import {
} from '../../util';
import EntityIcon from './EntityIcon';
import { ErrSelectionCanceled } from "../../util/errors";
const styles = {
mode: {
[TypeRect]: { backgroundColor: '#D32F2F' },
@ -316,13 +317,13 @@ class CollectSiteScreen extends React.Component {
onSavePress = () => {
const { dispatch } = this.props;
const { dispatch, entities } = this.props;
const { collect } = this;
const { mode } = this.state;
if (!collect.validate()) return;
selectEntityType(mode)
selectEntityType(entities, mode)
.then(entity => {
const positions = collect.getPositions();
const { site } = this.props;
@ -374,7 +375,8 @@ const mapStateToProp = (state, props) => {
notebookPosition: state.position.notebook,
moduleStatus: state.reachview.moduleStatus,
useNotebookGPS: state.settings.useNotebookGPS,
autoFollow: state.settings.autoFollow
autoFollow: state.settings.autoFollow,
entities: state.nomenclatures.entities,
}
};

View File

@ -21,14 +21,14 @@ import { ErrSelectionCanceled } from "../../util/errors";
class ExportSiteTab extends React.Component {
render() {
const { entities } = this.props;
const { entities, nomenclatures } = this.props;
const items = entities.map(e => {
return (
<ListItem icon key={e.uuid}>
<Left><EntityIcon type={e.metadata.type} /></Left>
<Body>
<Text>{ getEntityCodeLabel(e.metadata.code) }</Text>
<Text>{ getEntityCodeLabel(nomenclatures, e.metadata.code) }</Text>
<Text note>{ moment(e.metadata.creationDate).fromNow() }</Text>
</Body>
</ListItem>
@ -77,7 +77,8 @@ const mapStateToProp = (state, props) => {
const { siteUUID } = props;
return {
site: selectSiteByUUID(state.sites.byUUID, siteUUID),
entities: selectEntitiesBySiteUUID(state.entities.byUUID, siteUUID)
entities: selectEntitiesBySiteUUID(state.entities.byUUID, siteUUID),
nomenclatures: state.nomenclatures.entities,
}
};

View File

@ -5,12 +5,11 @@ import {
Text, Card, CardItem, Body,
Button, View,
} from 'native-base';
import { askPermissions, cleanupPlans } from '../../store';
import { askPermissions, cleanupPlans, startSync } from '../../store';
import { PermissionsAndroid } from 'react-native';
import MainRouter from './MainRouter';
import { askWriteExternalStoragePermission } from '../../util';
const { GRANTED, DENIED, NEVER_ASK_AGAIN } = PermissionsAndroid.RESULTS;
const { GRANTED, NEVER_ASK_AGAIN } = PermissionsAndroid.RESULTS;
const styles = {
@ -123,6 +122,7 @@ class SplashScreen extends React.Component {
componentDidUpdate(prevProps) {
if (!prevProps.isHydrated && this.props.isHydrated) {
this.props.dispatch(cleanupPlans());
this.props.dispatch(startSync());
}
}

View File

@ -5,4 +5,5 @@ export * from './position';
export * from './reachview';
export * from './entity';
export * from './permission';
export * from './settings';
export * from './settings';
export * from './sync';

View File

@ -0,0 +1,7 @@
export const SYNC_REQUEST = 'SYNC_REQUEST';
export const SYNC_SUCCESS = 'SYNC_SUCCESS';
export const SYNC_FAILURE = 'SYNC_FAILURE';
export function startSync() {
return { type: SYNC_REQUEST };
}

View File

@ -7,4 +7,5 @@ export positionReducer from './position';
export reachviewReducer from './reachview';
export entitiesReducer from './entities';
export permissionReducer from './permission';
export settingsReducer from './settings';
export settingsReducer from './settings';
export nomenclaturesReducer from './nomenclatures';

View File

@ -0,0 +1,23 @@
import defaultNomenclatures from '../../resources/nomenclatures.json';
import { SYNC_SUCCESS } from '../actions/sync.js';
const defaultState = {
entities: defaultNomenclatures.entities,
};
export default function nomenclaturesReducer(state = defaultState, action) {
switch(action.type) {
case SYNC_SUCCESS:
return handleSyncSuccess(state, action);
default:
return state;
}
}
function handleSyncSuccess(state, action) {
const { nomenclatures } = action;
return {
...state,
entities: [...nomenclatures.entities],
}
}

View File

@ -8,6 +8,7 @@ import reachviewReducer from './reachview';
import entitiesReducer from './entities';
import permissionReducer from './permission';
import settingsReducer from './settings';
import nomenclaturesReducer from './nomenclatures';
export default combineReducers({
wifiNetworks: wifiNetworksReducer,
@ -19,4 +20,5 @@ export default combineReducers({
entities: entitiesReducer,
permission: permissionReducer,
settings: settingsReducer,
nomenclatures: nomenclaturesReducer,
});

View File

@ -7,6 +7,7 @@ const defaultState = {
wifiPassword: 'emlidreach',
useNotebookGPS: false,
autoFollow: false,
nomenclaturesURL: 'https://fieldnotes.dev.lookingfora.name/field-surveys/nomenclatures.json'
};
export default function settingsReducer(state = defaultState, action) {

View File

@ -3,6 +3,7 @@ import { permissionSaga } from './permission';
import { reachviewSaga } from './reachview';
import { positionSaga } from './position';
import { planSaga } from './plan';
import { syncSaga } from './sync';
export function* rootSaga() {
yield all([
@ -10,5 +11,6 @@ export function* rootSaga() {
reachviewSaga(),
positionSaga(),
planSaga(),
syncSaga()
]);
}

49
src/store/sagas/sync.js Normal file
View File

@ -0,0 +1,49 @@
import { takeLatest, select, put, all, call } from 'redux-saga/effects'
import {
SYNC_REQUEST,
SYNC_FAILURE,
SYNC_SUCCESS
} from '../actions';
import { NetInfo } from 'react-native';
import { Sentry } from 'react-native-sentry';
export function* syncSaga() {
yield all([
takeLatest(SYNC_REQUEST, doSync),
])
}
function* doSync() {
try {
const isConnected = yield NetInfo.isConnected.fetch();
if (!isConnected) {
yield put({ type: SYNC_FAILURE, err: new Error('No internet connectivity.') });
return
}
const [ nomenclatures ] = yield all([
fetchNomenclaturesSaga(),
])
yield put({ type: SYNC_SUCCESS, nomenclatures });
} catch (err) {
Sentry.captureException(err);
yield put({ type: SYNC_FAILURE, err });
}
}
function* fetchNomenclaturesSaga() {
const nomenclaturesURL = yield select(state => state.settings.nomenclaturesURL);
const res = yield call(fetch, nomenclaturesURL, {
headers: {
'Pragma': 'no-cache',
'Cache-Control': 'no-cache'
}
});
const nomenclatures = yield res.json();
return nomenclatures;
}

View File

@ -30,7 +30,7 @@ const store = createStore(
...offlineConfig,
persistOptions: {
...offlineConfig.persistOptions,
whitelist: ['sites', 'entities', 'settings']
whitelist: ['sites', 'entities', 'settings', 'nomenclatures']
}
}),
applyMiddleware(...reduxMiddlewares)

View File

@ -1,4 +1,3 @@
import nomenclatures from '../resources/nomenclatures.json';
import { ActionSheet } from 'native-base';
import { ErrSelectionCanceled } from './errors';
@ -7,9 +6,9 @@ export const TypeCurve = 'curve';
export const TypeRect = 'rect';
export const TypeCircle = 'circle';
export function selectEntityType(typeFilter) {
export function selectEntityType(entities, typeFilter) {
const buttons = nomenclatures.entities.reduce((buttons, entityCategory) => {
const buttons = entities.reduce((buttons, entityCategory) => {
// Iterate over entity categories
buttons.push(...entityCategory.types.reduce((buttons, entityType) => {
@ -48,8 +47,7 @@ export function selectEntityType(typeFilter) {
});
}
export function getEntityCategoryLabel(category) {
const { entities } = nomenclatures;
export function getEntityCategoryLabel(entities, category) {
for (let c, i = 0; (c = entities[i]); ++i) {
if (c.category === category) {
return c.label;
@ -58,8 +56,7 @@ export function getEntityCategoryLabel(category) {
return `Catégorie inconnue (${category})`;
}
export function getEntityCodeLabel(code) {
const { entities } = nomenclatures;
export function getEntityCodeLabel(entities, code) {
for (let c, i = 0; (c = entities[i]); ++i) {
for (let e, j = 0; (e = c.types[j]); ++j) {
if (e.code === code) {