Change journals to also not be a complex fetch type.

master
Tom Hacohen 5 years ago
parent 23048cfa63
commit 2946be464c

@ -16,7 +16,7 @@ export default function Debug(props: PropsType) {
const [stateJournalUid, setJournalUid] = React.useState(''); const [stateJournalUid, setJournalUid] = React.useState('');
const [entriesUids, setEntriesUids] = React.useState(''); const [entriesUids, setEntriesUids] = React.useState('');
const [result, setResult] = React.useState(''); const [result, setResult] = React.useState('');
const journals = useSelector((state: StoreState) => state.cache.journals.value)!; const journals = useSelector((state: StoreState) => state.cache.journals!);
const journalEntries = useSelector((state: StoreState) => state.cache.entries); const journalEntries = useSelector((state: StoreState) => state.cache.entries);
function handleInputChange(func: (value: string) => void) { function handleInputChange(func: (value: string) => void) {

@ -16,7 +16,7 @@ import logo from '../images/logo.svg';
import { routeResolver } from '../App'; import { routeResolver } from '../App';
import { store, JournalsType, UserInfoData, StoreState, CredentialsData } from '../store'; import { store, UserInfoData, StoreState, CredentialsData } from '../store';
import { logout } from '../store/actions'; import { logout } from '../store/actions';
import * as C from '../constants'; import * as C from '../constants';
@ -27,7 +27,6 @@ interface PropsType {
} }
type PropsTypeInner = RouteComponentProps<{}> & PropsType & { type PropsTypeInner = RouteComponentProps<{}> & PropsType & {
journals: JournalsType;
userInfo: UserInfoData; userInfo: UserInfoData;
theme: Theme; theme: Theme;
}; };
@ -114,7 +113,6 @@ class SideMenu extends React.PureComponent<PropsTypeInner> {
const mapStateToProps = (state: StoreState, _props: PropsType) => { const mapStateToProps = (state: StoreState, _props: PropsType) => {
return { return {
journals: state.cache.journals,
userInfo: state.cache.userInfo, userInfo: state.cache.userInfo,
}; };
}; };

@ -12,7 +12,6 @@ import { routeResolver } from './App';
import AppBarOverride from './widgets/AppBarOverride'; import AppBarOverride from './widgets/AppBarOverride';
import LoadingIndicator from './widgets/LoadingIndicator'; import LoadingIndicator from './widgets/LoadingIndicator';
import PrettyError from './widgets/PrettyError';
import Journals from './Journals'; import Journals from './Journals';
import Settings from './Settings'; import Settings from './Settings';
@ -22,7 +21,7 @@ import Pim from './Pim';
import * as EteSync from 'etesync'; import * as EteSync from 'etesync';
import { CURRENT_VERSION } from 'etesync'; import { CURRENT_VERSION } from 'etesync';
import { store, SettingsType, JournalsType, EntriesType, StoreState, CredentialsData, UserInfoData } from './store'; import { store, SettingsType, JournalsData, EntriesType, StoreState, CredentialsData, UserInfoData } from './store';
import { addJournal, fetchAll, fetchEntries, fetchUserInfo, createUserInfo } from './store/actions'; import { addJournal, fetchAll, fetchEntries, fetchUserInfo, createUserInfo } from './store/actions';
export interface SyncInfoJournal { export interface SyncInfoJournal {
@ -40,7 +39,7 @@ interface PropsType {
type PropsTypeInner = RouteComponentProps<{}> & PropsType & { type PropsTypeInner = RouteComponentProps<{}> & PropsType & {
settings: SettingsType; settings: SettingsType;
journals: JournalsType; journals: JournalsData;
entries: EntriesType; entries: EntriesType;
userInfo: UserInfoData; userInfo: UserInfoData;
fetchCount: number; fetchCount: number;
@ -48,7 +47,7 @@ type PropsTypeInner = RouteComponentProps<{}> & PropsType & {
const syncInfoSelector = createSelector( const syncInfoSelector = createSelector(
(props: PropsTypeInner) => props.etesync, (props: PropsTypeInner) => props.etesync,
(props: PropsTypeInner) => props.journals.value!, (props: PropsTypeInner) => props.journals!,
(props: PropsTypeInner) => props.entries, (props: PropsTypeInner) => props.entries,
(props: PropsTypeInner) => props.userInfo, (props: PropsTypeInner) => props.userInfo,
(etesync, journals, entries, userInfo) => { (etesync, journals, entries, userInfo) => {
@ -153,11 +152,9 @@ class SyncGate extends React.PureComponent<PropsTypeInner> {
public render() { public render() {
const entryArrays = this.props.entries; const entryArrays = this.props.entries;
const journals = this.props.journals.value; const journals = this.props.journals;
if (this.props.journals.error) { {
return <PrettyError error={this.props.journals.error} />;
} else {
const errors: Array<{journal: string, error: Error}> = []; const errors: Array<{journal: string, error: Error}> = [];
this.props.entries.forEach((entry, journal) => { this.props.entries.forEach((entry, journal) => {
if (entry.error) { if (entry.error) {

@ -7,8 +7,8 @@ import { List, Map as ImmutableMap } from 'immutable';
import * as EteSync from 'etesync'; import * as EteSync from 'etesync';
import { import {
JournalsData, FetchType, EntriesData, EntriesFetchRecord, UserInfoData, JournalsFetchRecord, JournalsData, FetchType, EntriesData, EntriesFetchRecord, UserInfoData,
CredentialsDataRemote, JournalsType, EntriesType, SettingsType, CredentialsDataRemote, EntriesType, SettingsType,
fetchCount, journals, entries, credentials, userInfo, settingsReducer, encryptionKeyReducer, errorsReducer, fetchCount, journals, entries, credentials, userInfo, settingsReducer, encryptionKeyReducer, errorsReducer,
} from './reducers'; } from './reducers';
@ -18,7 +18,7 @@ export interface StoreState {
settings: SettingsType; settings: SettingsType;
encryptionKey: {key: string}; encryptionKey: {key: string};
cache: { cache: {
journals: JournalsType; journals: JournalsData;
entries: EntriesType; entries: EntriesType;
userInfo: UserInfoData; userInfo: UserInfoData;
}; };
@ -49,19 +49,19 @@ const journalsSerialize = (state: JournalsData) => {
return state.map((x, _uid) => x.serialize()).toJS(); return state.map((x, _uid) => x.serialize()).toJS();
}; };
const journalsDeserialize = (state: {}) => { const journalsDeserialize = (state: []) => {
if (state === null) { if (state === null) {
return null; return null;
} }
const newState = new Map<string, EteSync.Journal>(); const newState = ImmutableMap<string, EteSync.Journal>().asMutable();
Object.keys(state).forEach((uid) => { Object.keys(state).forEach((uid) => {
const x = state[uid]; const x = state[uid];
const ret = new EteSync.Journal({ uid }, x.version); const ret = new EteSync.Journal({ uid }, x.version);
ret.deserialize(x); ret.deserialize(x);
newState.set(uid, ret); newState.set(uid, ret);
}); });
return ImmutableMap(newState); return newState.asImmutable();
}; };
const entriesSerialize = (state: FetchType<EntriesData>) => { const entriesSerialize = (state: FetchType<EntriesData>) => {
@ -110,7 +110,7 @@ const cacheSerialize = (state: any, key: string) => {
}); });
return ret; return ret;
} else if (key === 'journals') { } else if (key === 'journals') {
return journalsSerialize(state.value); return journalsSerialize(state);
} else if (key === 'userInfo') { } else if (key === 'userInfo') {
return userInfoSerialize(state); return userInfoSerialize(state);
} }
@ -126,7 +126,7 @@ const cacheDeserialize = (state: any, key: string) => {
}); });
return ImmutableMap(ret); return ImmutableMap(ret);
} else if (key === 'journals') { } else if (key === 'journals') {
return new JournalsFetchRecord({ value: journalsDeserialize(state) }); return journalsDeserialize(state);
} else if (key === 'userInfo') { } else if (key === 'userInfo') {
return userInfoDeserialize(state); return userInfoDeserialize(state);
} }

@ -1,4 +1,4 @@
import { Action, ActionFunctionAny, combineActions, handleAction, handleActions } from 'redux-actions'; import { Action, ActionMeta, ActionFunctionAny, combineActions, handleAction, handleActions } from 'redux-actions';
import { shallowEqual } from 'react-redux'; import { shallowEqual } from 'react-redux';
import { List, Map as ImmutableMap, Record } from 'immutable'; import { List, Map as ImmutableMap, Record } from 'immutable';
@ -31,14 +31,7 @@ function fetchTypeRecord<T>() {
}); });
} }
interface BaseModel {
uid: string;
}
export type JournalsData = ImmutableMap<string, EteSync.Journal>; export type JournalsData = ImmutableMap<string, EteSync.Journal>;
export const JournalsFetchRecord = fetchTypeRecord<JournalsData>();
export type JournalsType = FetchType<JournalsData>;
export type JournalsTypeImmutable = Record<JournalsType>;
export type EntriesData = List<EteSync.Entry>; export type EntriesData = List<EteSync.Entry>;
export const EntriesFetchRecord = fetchTypeRecord<EntriesData>(); export const EntriesFetchRecord = fetchTypeRecord<EntriesData>();
@ -112,105 +105,100 @@ export const credentials = handleActions(
{} as CredentialsDataRemote {} as CredentialsDataRemote
); );
const setMapModelReducer = <T extends Record<any>, V extends BaseModel>(state: T, action: any) => { function fetchCreateEntriesReducer(state: EntriesTypeImmutable, action: any) {
const newState = fetchTypeIdentityReducer(state, action); const prevState = state.get(action.meta.journal);
// Compare the states and see if they are really different const extend = action.meta.prevUid != null;
const newItems = newState.get('value', null); return state.set(action.meta.journal,
fetchTypeIdentityReducer(prevState, action, extend));
}
if (!newItems) { export const entries = handleActions(
return newState; {
[actions.fetchEntries.toString()]: fetchCreateEntriesReducer,
[actions.addEntries.toString()]: fetchCreateEntriesReducer,
[actions.addJournal.toString()]: (state: EntriesTypeImmutable, action: any) => {
const journal = action.meta.item.uid;
const prevState = state.get(journal);
return state.set(journal,
fetchTypeIdentityReducer(prevState, { payload: [] }, false));
},
},
ImmutableMap({})
);
const setMapModelReducer = (state: JournalsData, action: Action<EteSync.Journal[]>) => {
if (action.error || !action.payload) {
return state;
} }
const ret = new Map<string, V>(); state = state ?? ImmutableMap<string, EteSync.Journal>().asMutable();
const old = state.asMutable();
newItems.forEach((item: V) => { return state.withMutations((ret) => {
const items = action.payload!;
for (const item of items) {
const current = old.get(item.uid);
if (!current || !shallowEqual(current.serialize(), item.serialize())) {
ret.set(item.uid, item); ret.set(item.uid, item);
}); }
return newState.set('value', ImmutableMap(ret)); if (current) {
old.delete(item.uid);
}
}
// Delete all the items that were deleted remotely (not handled above).
for (const uid of old.keys()) {
ret.delete(uid);
}
});
}; };
const addEditMapModelReducer = <T extends Record<any>, V extends BaseModel>(state: T, action: any) => { const addEditMapModelReducer = (state: JournalsData, action: ActionMeta<EteSync.Journal, { item: EteSync.Journal }>) => {
if (action.error) { if (action.error) {
return state.set('error', action.payload); return state;
} else { } else {
let payload = (action.payload === undefined) ? null : action.payload; let payload = (action.payload === undefined) ? null : action.payload;
payload = (action.meta === undefined) ? payload : action.meta.item; payload = (action.meta === undefined) ? payload : action.meta.item;
state = state.set('error', undefined); if (!payload) {
if (action.payload === undefined) {
return state; return state;
} }
const item = payload as V; const item = payload;
let value = state.get('value', null)!; return state.set(item.uid, item);
value = value.set(item.uid, item);
return state.set('value', value);
} }
}; };
const deleteMapModelReducer = <T extends Record<any>>(state: T, action: any) => { const deleteMapModelReducer = (state: JournalsData, action: ActionMeta<EteSync.Journal, { item: EteSync.Journal }>) => {
if (action.error) { if (action.error) {
return state.set('error', action.payload); return state;
} else { } else {
let payload = (action.payload === undefined) ? null : action.payload; let payload = (action.payload === undefined) ? null : action.payload;
payload = (action.meta === undefined) ? payload : action.meta.item; payload = (action.meta === undefined) ? payload : action.meta.item;
state = state.set('error', undefined); if (!payload) {
if (action.payload === undefined) {
return state; return state;
} }
const uid = payload.uid; const uid = payload.uid;
let value = state.get('value', null)!; return state.delete(uid);
value = value.delete(uid);
return state.set('value', value);
} }
}; };
const mapReducerActionsMapCreator = <T extends Record<any>, V extends BaseModel>(actionName: string) => { export const journals = handleActions(
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,
};
};
function fetchCreateEntriesReducer(state: EntriesTypeImmutable, action: any) {
const prevState = state.get(action.meta.journal);
const extend = action.meta.prevUid != null;
return state.set(action.meta.journal,
fetchTypeIdentityReducer(prevState, action, extend));
}
export const entries = handleActions(
{ {
[actions.fetchEntries.toString()]: fetchCreateEntriesReducer, [actions.fetchListJournal.toString()]: setMapModelReducer as any,
[actions.addEntries.toString()]: fetchCreateEntriesReducer, [actions.addJournal.toString()]: addEditMapModelReducer,
[actions.addJournal.toString()]: (state: EntriesTypeImmutable, action: any) => { [actions.updateJournal.toString()]: addEditMapModelReducer,
const journal = action.meta.item.uid; [actions.deleteJournal.toString()]: deleteMapModelReducer,
const prevState = state.get(journal); [actions.logout.toString()]: (state: JournalsData, _action: any) => {
return state.set(journal, return state.clear();
fetchTypeIdentityReducer(prevState, { payload: [] }, false));
}, },
}, },
ImmutableMap({}) ImmutableMap({})
); );
export const journals = handleActions(
{
...mapReducerActionsMapCreator<JournalsTypeImmutable, EteSync.Journal>('Journal'),
},
new JournalsFetchRecord()
);
export const userInfo = handleActions( export const userInfo = handleActions(
{ {
[combineActions( [combineActions(

@ -11,6 +11,7 @@
"allowJs": true, "allowJs": true,
"jsx": "preserve", "jsx": "preserve",
"rootDir": "src", "rootDir": "src",
"downlevelIteration": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"noUnusedLocals": true, "noUnusedLocals": true,
"moduleResolution": "node", "moduleResolution": "node",

Loading…
Cancel
Save