Jounal store: simplify the store functions and change the list to a hash

It's always been a massive mess, this improves it.
master
Tom Hacohen 6 years ago
parent 5b79e0f107
commit c5fc6f23f5

@ -10,7 +10,7 @@ import AppBarOverride from '../widgets/AppBarOverride';
import { routeResolver } from '../App';
import { store, JournalsData, UserInfoData, CredentialsData } from '../store';
import { createJournal, updateJournal } from '../store/actions';
import { addJournal, updateJournal } from '../store/actions';
import { SyncInfo } from '../SyncGate';
import * as EteSync from '../api/EteSync';
@ -102,7 +102,7 @@ class Journals extends React.PureComponent {
this.props.history.goBack()
);
} else {
store.dispatch<any>(createJournal(this.props.etesync, journal)).then(() =>
store.dispatch<any>(addJournal(this.props.etesync, journal)).then(() =>
this.props.history.goBack()
);
}

@ -19,7 +19,7 @@ import * as EteSync from './api/EteSync';
import { CURRENT_VERSION } from './api/Constants';
import { store, JournalsType, EntriesType, StoreState, CredentialsData, UserInfoType } from './store';
import { createJournal, fetchAll, fetchEntries, fetchUserInfo, createUserInfo } from './store/actions';
import { addJournal, fetchAll, fetchEntries, fetchUserInfo, createUserInfo } from './store/actions';
export interface SyncInfoJournal {
journal: EteSync.Journal;
@ -43,7 +43,7 @@ type PropsTypeInner = RouteComponentProps<{}> & PropsType & {
const syncInfoSelector = createSelector(
(props: PropsTypeInner) => props.etesync,
(props: PropsTypeInner) => props.journals.value as List<EteSync.Journal>,
(props: PropsTypeInner) => props.journals.value!,
(props: PropsTypeInner) => props.entries,
(props: PropsTypeInner) => props.userInfo.value!,
(etesync, journals, entries, userInfo) => {
@ -115,7 +115,7 @@ class SyncGate extends React.PureComponent<PropsTypeInner> {
const journal = new EteSync.Journal();
const cryptoManager = new EteSync.CryptoManager(this.props.etesync.encryptionKey, collection.uid);
journal.setInfo(cryptoManager, collection);
store.dispatch<any>(createJournal(this.props.etesync, journal)).then(
store.dispatch<any>(addJournal(this.props.etesync, journal)).then(
(journalAction: Action<EteSync.Journal>) => {
// FIXME: Limit based on error code to only do it for associates.
if (!journalAction.error) {

@ -3,7 +3,7 @@ import { createAction, createActions } from 'redux-actions';
import * as EteSync from '../api/EteSync';
import { UserInfo } from '../api/EteSync';
import { CredentialsData, EntriesType } from './';
import { CredentialsData, EntriesType, JournalsData } from './';
export const { fetchCredentials, logout } = createActions({
FETCH_CREDENTIALS: (username: string, password: string, server: string) => {
@ -44,8 +44,8 @@ export const login = (username: string, password: string, encryptionPassword: st
};
};
export const { fetchJournals } = createActions({
FETCH_JOURNALS: (etesync: CredentialsData) => {
export const { fetchListJournal } = createActions({
FETCH_LIST_JOURNAL: (etesync: CredentialsData) => {
const creds = etesync.credentials;
const apiBase = etesync.serviceApiUrl;
let journalManager = new EteSync.JournalManager(creds, apiBase);
@ -54,8 +54,8 @@ export const { fetchJournals } = createActions({
},
});
export const createJournal = createAction(
'CREATE_JOURNAL',
export const addJournal = createAction(
'ADD_JOURNAL',
(etesync: CredentialsData, journal: EteSync.Journal) => {
const creds = etesync.credentials;
const apiBase = etesync.serviceApiUrl;
@ -64,7 +64,7 @@ export const createJournal = createAction(
return journalManager.create(journal);
},
(etesync: CredentialsData, journal: EteSync.Journal) => {
return { journal };
return { item: journal };
},
);
@ -78,7 +78,21 @@ export const updateJournal = createAction(
return journalManager.update(journal);
},
(etesync: CredentialsData, journal: EteSync.Journal) => {
return { journal };
return { item: journal };
},
);
export const deleteJournal = createAction(
'DELETE_JOURNAL',
(etesync: CredentialsData, journal: EteSync.Journal) => {
const creds = etesync.credentials;
const apiBase = etesync.serviceApiUrl;
let journalManager = new EteSync.JournalManager(creds, apiBase);
return journalManager.delete(journal);
},
(etesync: CredentialsData, journal: EteSync.Journal) => {
return { item: journal };
},
);
@ -135,13 +149,13 @@ export const createUserInfo = createAction(
export function fetchAll(etesync: CredentialsData, currentEntries: EntriesType) {
return (dispatch: any) => {
return dispatch(fetchJournals(etesync)).then((journalsAction: any) => {
const journals: Array<EteSync.Journal> = journalsAction.payload;
if (!journals || (journals.length === 0)) {
return dispatch(fetchListJournal(etesync)).then((journalsAction: any) => {
const journals: JournalsData = journalsAction.payload;
if (!journals || journals.isEmpty) {
return false;
}
journals.forEach((journal) => {
journals.forEach((journal, uid) => {
let prevUid: string | null = null;
const entries = currentEntries.get(journal.uid);
if (entries && entries.value) {

@ -1,11 +1,11 @@
import { combineReducers } from 'redux';
import { persistReducer, createTransform } from 'redux-persist';
import { handleAction, handleActions, combineActions } from 'redux-actions';
import { createMigrate, persistReducer, createTransform } from 'redux-persist';
import { Action, ActionFunctionAny, combineActions, handleAction, handleActions } from 'redux-actions';
import * as localforage from 'localforage';
import session from 'redux-persist/lib/storage/session';
import { List, Map, Record } from 'immutable';
import { List, Map as ImmutableMap, Record } from 'immutable';
import * as EteSync from '../api/EteSync';
@ -35,11 +35,14 @@ function fetchTypeRecord<T>() {
});
}
interface BaseModel {
uid: string;
}
export type CredentialsType = FetchType<CredentialsData>;
export type CredentialsTypeRemote = FetchType<CredentialsDataRemote>;
export type JournalsData = List<EteSync.Journal>;
export type JournalsData = ImmutableMap<string, EteSync.Journal>;
const JournalsFetchRecord = fetchTypeRecord<JournalsData>();
export type JournalsType = FetchType<JournalsData>;
export type JournalsTypeImmutable = Record<JournalsType>;
@ -48,8 +51,8 @@ export type EntriesData = List<EteSync.Entry>;
const EntriesFetchRecord = fetchTypeRecord<EntriesData>();
export type EntriesTypeImmutable = Map<string, Record<FetchType<EntriesData>>>;
export type EntriesType = Map<string, FetchType<EntriesData>>;
export type EntriesTypeImmutable = ImmutableMap<string, Record<FetchType<EntriesData>>>;
export type EntriesType = ImmutableMap<string, FetchType<EntriesData>>;
export type UserInfoData = EteSync.UserInfo;
@ -127,6 +130,77 @@ const credentials = handleActions(
{value: null}
);
const setMapModelReducer = <T extends Record<any>, V extends BaseModel>(state: T, action: any) => {
const newState = fetchTypeIdentityReducer(state, action);
// Compare the states and see if they are really different
const newItems = newState.get('value', null);
if (!newItems) {
return newState;
}
const ret = new Map<string, V>();
newItems.forEach((item: V) => {
ret.set(item.uid, item);
});
return newState.set('value', ImmutableMap(ret));
};
const addEditMapModelReducer = <T extends Record<any>, V extends BaseModel>(state: T, action: any) => {
if (action.error) {
return state.set('error', action.payload);
} else {
let payload = (action.payload === undefined) ? null : action.payload;
payload = (action.meta === undefined) ? payload : action.meta.item;
state = state.set('error', undefined);
if (action.payload === undefined) {
return state;
}
const item = payload as V;
let value = state.get('value', null)!;
value = value.set(item.uid, item);
return state.set('value', value);
}
};
const deleteMapModelReducer = <T extends Record<any>>(state: T, action: any) => {
if (action.error) {
return state.set('error', action.payload);
} else {
let payload = (action.payload === undefined) ? null : action.payload;
payload = (action.meta === undefined) ? payload : action.meta.item;
state = state.set('error', undefined);
if (action.payload === undefined) {
return state;
}
const id = payload as number;
let value = state.get('value', null)!;
value = value.delete(id);
return state.set('value', value);
}
};
const mapReducerActionsMapCreator = <T extends Record<any>, V extends BaseModel>(actionName: string) => {
const setsReducer = (state: T, action: any) => setMapModelReducer<T, V>(state, action);
const addEditReducer = (state: T, action: any) => addEditMapModelReducer<T, V>(state, action);
const deleteReducer = (state: T, action: any) => deleteMapModelReducer<T>(state, action);
return {
[actions['fetchList' + actionName].toString() as string]: setsReducer,
[actions['add' + actionName].toString() as string]: addEditReducer,
[actions['update' + actionName].toString() as string]: addEditReducer,
[actions['delete' + actionName].toString() as string]: deleteReducer,
};
};
export const entries = handleAction(
combineActions(actions.fetchEntries, actions.createEntries),
(state: EntriesTypeImmutable, action: any) => {
@ -135,95 +209,12 @@ export const entries = handleAction(
return state.set(action.meta.journal,
fetchTypeIdentityReducer(prevState, action, extend));
},
Map({})
ImmutableMap({})
);
const journals = handleActions(
{
[actions.fetchJournals.toString()]: (state: JournalsTypeImmutable, action: any) => {
const newState = fetchTypeIdentityReducer(state, action);
// Compare the states and see if they are really different
const oldJournals = state.get('value', null);
const newJournals = newState.get('value', null);
if (!oldJournals || !newJournals || (oldJournals.size !== newJournals.size)) {
return newState;
}
let oldJournalHash = {};
oldJournals.forEach((x) => {
oldJournalHash[x.uid] = x.serialize();
});
if (newJournals.every((journal: EteSync.Journal) => (
(journal.uid in oldJournalHash) &&
(journal.serialize().content === oldJournalHash[journal.uid].content)
))) {
return state;
} else {
return newState;
}
},
[actions.createJournal.toString()]: (state: JournalsTypeImmutable, _action: any) => {
const action = { ..._action };
if (action.payload) {
action.payload = (action.meta === undefined) ? action.payload : action.meta.journal;
action.payload = [ action.payload ];
}
const newState = fetchTypeIdentityReducer(state, action, true);
// Compare the states and see if they are really different
const oldJournals = state.get('value', null);
const newJournals = newState.get('value', null);
if (!oldJournals || !newJournals || (oldJournals.size !== newJournals.size)) {
return newState;
}
let oldJournalHash = {};
oldJournals.forEach((x) => {
oldJournalHash[x.uid] = x.serialize();
});
if (newJournals.every((journal: EteSync.Journal) => (
(journal.uid in oldJournalHash) &&
(journal.serialize().content === oldJournalHash[journal.uid].content)
))) {
return state;
} else {
return newState;
}
},
[actions.updateJournal.toString()]: (state: JournalsTypeImmutable, _action: any) => {
const action = { ..._action };
if (action.payload) {
action.payload = (action.meta === undefined) ? action.payload : action.meta.journal;
action.payload = [ action.payload ];
}
const newState = fetchTypeIdentityReducer(state, action, true);
// Compare the states and see if they are really different
const oldJournals = state.get('value', null);
const newJournals = newState.get('value', null);
if (!oldJournals || !newJournals || (oldJournals.size !== newJournals.size)) {
return newState;
}
let oldJournalHash = {};
oldJournals.forEach((x) => {
oldJournalHash[x.uid] = x.serialize();
});
if (newJournals.every((journal: EteSync.Journal) => (
(journal.uid in oldJournalHash) &&
(journal.serialize().content === oldJournalHash[journal.uid].content)
))) {
return state;
} else {
return newState;
}
},
...mapReducerActionsMapCreator<JournalsTypeImmutable, EteSync.Journal>('Journal'),
},
new JournalsFetchRecord(),
);
@ -253,11 +244,23 @@ const userInfo = handleAction(
new JournalsFetchRecord(),
);
const fetchActions = [
] as Array<ActionFunctionAny<Action<any>>>;
for (const func in actions) {
if (func.startsWith('fetchList') ||
func.startsWith('add') ||
func.startsWith('update') ||
func.startsWith('delete')) {
fetchActions.push(actions[func]);
}
}
// Indicates network activity, not just fetch
const fetchCount = handleAction(
combineActions(
actions.fetchCredentials,
actions.fetchJournals,
actions.fetchEntries
...fetchActions,
),
(state: number, action: any) => {
if (action.payload === undefined) {
@ -266,7 +269,7 @@ const fetchCount = handleAction(
return state - 1;
}
},
0
0,
);
const credentialsPersistConfig = {
@ -285,19 +288,22 @@ const journalsSerialize = (state: JournalsData) => {
return null;
}
return state.map((x) => x.serialize()).toJS();
return state.map((x, uid) => x.serialize()).toJS();
};
const journalsDeserialize = (state: EteSync.JournalJson[]) => {
const journalsDeserialize = (state: {}) => {
if (state === null) {
return null;
}
return List(state.map((x: any) => {
let ret = new EteSync.Journal(x.version);
const newState = new Map<string, EteSync.Journal>();
Object.keys(state).forEach((uid) => {
const x = state[uid];
const ret = new EteSync.Journal(x.version);
ret.deserialize(x);
return ret;
}));
newState.set(uid, ret);
});
return ImmutableMap(newState);
};
const entriesSerialize = (state: FetchType<EntriesData>) => {
@ -360,7 +366,7 @@ const cacheDeserialize = (state: any, key: string) => {
Object.keys(state).forEach((mapKey) => {
ret[mapKey] = entriesDeserialize(state[mapKey]);
});
return Map(ret);
return ImmutableMap(ret);
} else if (key === 'journals') {
return new JournalsFetchRecord({value: journalsDeserialize(state)});
} else if (key === 'userInfo') {
@ -370,10 +376,21 @@ const cacheDeserialize = (state: any, key: string) => {
return state;
};
const cacheMigrations = {
0: (state: any) => {
return {
...state,
journals: undefined
};
},
};
const cachePersistConfig = {
key: 'cache',
version: 1,
storage: localforage,
transforms: [createTransform(cacheSerialize, cacheDeserialize)],
migrate: createMigrate(cacheMigrations, { debug: false}),
};
const reducers = combineReducers({

Loading…
Cancel
Save