Change entries to also not be a complex fetch type.

master
Tom Hacohen 5 years ago
parent 2946be464c
commit 90392fe432

@ -172,7 +172,7 @@ class App extends React.PureComponent {
public props: { public props: {
credentials: store.CredentialsData; credentials: store.CredentialsData;
entries: store.EntriesType; entries: store.EntriesData;
fetchCount: number; fetchCount: number;
errors: ImmutableList<Error>; errors: ImmutableList<Error>;
}; };

@ -5,7 +5,7 @@ import TextField from '@material-ui/core/TextField';
import Container from './widgets/Container'; import Container from './widgets/Container';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { StoreState, CredentialsData, UserInfoData } from './store'; import { StoreState, CredentialsData, UserInfoData, EntriesListData } from './store';
interface PropsType { interface PropsType {
etesync: CredentialsData; etesync: CredentialsData;
@ -67,8 +67,8 @@ export default function Debug(props: PropsType) {
const cryptoManager = journal.getCryptoManager(derived, keyPair); const cryptoManager = journal.getCryptoManager(derived, keyPair);
let prevUid: string | null = null; let prevUid: string | null = null;
const entries = journalEntries.get(journalUid)!; const entries = journalEntries.get(journalUid)! as EntriesListData;
const syncEntries = entries.value!.map((entry: EteSync.Entry) => { const syncEntries = entries.map((entry: EteSync.Entry) => {
const syncEntry = entry.getSyncEntry(cryptoManager, prevUid); const syncEntry = entry.getSyncEntry(cryptoManager, prevUid);
prevUid = entry.uid; prevUid = entry.uid;

@ -21,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, JournalsData, EntriesType, StoreState, CredentialsData, UserInfoData } from './store'; import { store, SettingsType, JournalsData, EntriesData, 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 +40,7 @@ interface PropsType {
type PropsTypeInner = RouteComponentProps<{}> & PropsType & { type PropsTypeInner = RouteComponentProps<{}> & PropsType & {
settings: SettingsType; settings: SettingsType;
journals: JournalsData; journals: JournalsData;
entries: EntriesType; entries: EntriesData;
userInfo: UserInfoData; userInfo: UserInfoData;
fetchCount: number; fetchCount: number;
}; };
@ -68,7 +68,7 @@ const syncInfoSelector = createSelector(
const journalEntries = entries.get(journal.uid); const journalEntries = entries.get(journal.uid);
let prevUid: string | null = null; let prevUid: string | null = null;
if (!journalEntries || !journalEntries.value) { if (!journalEntries) {
return ret; return ret;
} }
@ -77,7 +77,7 @@ const syncInfoSelector = createSelector(
const collectionInfo = journal.getInfo(cryptoManager); const collectionInfo = journal.getInfo(cryptoManager);
const syncEntries = journalEntries.value.map((entry: EteSync.Entry) => { const syncEntries = journalEntries.map((entry: EteSync.Entry) => {
const syncEntry = entry.getSyncEntry(cryptoManager, prevUid); const syncEntry = entry.getSyncEntry(cryptoManager, prevUid);
prevUid = entry.uid; prevUid = entry.uid;
@ -88,7 +88,7 @@ const syncInfoSelector = createSelector(
entries: syncEntries, entries: syncEntries,
collection: collectionInfo, collection: collectionInfo,
journal, journal,
journalEntries: journalEntries.value, journalEntries,
}); });
}, },
Map<string, SyncInfoJournal>() Map<string, SyncInfoJournal>()
@ -154,26 +154,9 @@ class SyncGate extends React.PureComponent<PropsTypeInner> {
const entryArrays = this.props.entries; const entryArrays = this.props.entries;
const journals = this.props.journals; const journals = this.props.journals;
{
const errors: Array<{journal: string, error: Error}> = [];
this.props.entries.forEach((entry, journal) => {
if (entry.error) {
errors.push({ journal, error: entry.error });
}
});
if (errors.length > 0) {
return (
<ul>
{errors.map((error, idx) => (<li key={idx}>{error.journal}: {error.error.toString()}</li>))}
</ul>
);
}
}
if ((this.props.userInfo === null) || (journals === null) || if ((this.props.userInfo === null) || (journals === null) ||
((this.props.fetchCount > 0) && ((this.props.fetchCount > 0) &&
((entryArrays.size === 0) || !entryArrays.every((x: any) => (x.value !== null)))) ((entryArrays.size === 0) || entryArrays.some((x) => (x.size === 0))))
) { ) {
return (<LoadingIndicator style={{ display: 'block', margin: '40px auto' }} />); return (<LoadingIndicator style={{ display: 'block', margin: '40px auto' }} />);
} }

@ -3,7 +3,7 @@ import { Action, createAction, createActions } from 'redux-actions';
import * as EteSync from 'etesync'; import * as EteSync from 'etesync';
import { UserInfo } from 'etesync'; import { UserInfo } from 'etesync';
import { CredentialsData, EntriesType, SettingsType } from './'; import { CredentialsData, EntriesData, SettingsType } from './';
export const { fetchCredentials } = createActions({ export const { fetchCredentials } = createActions({
FETCH_CREDENTIALS: (username: string, password: string, server: string) => { FETCH_CREDENTIALS: (username: string, password: string, server: string) => {
@ -168,21 +168,7 @@ export const createUserInfo = createAction(
} }
); );
export function fetchJournalEntries(etesync: CredentialsData, currentEntries: EntriesType, journal: EteSync.Journal) { export function fetchAll(etesync: CredentialsData, currentEntries: EntriesData) {
return (dispatch: any) => {
let prevUid: string | null = null;
const entries = currentEntries.get(journal.uid);
if (entries && entries.value) {
const last = entries.value.last() as EteSync.Entry;
prevUid = (last) ? last.uid : null;
}
return dispatch(fetchEntries(etesync, journal.uid, prevUid));
};
}
export function fetchAll(etesync: CredentialsData, currentEntries: EntriesType) {
return (dispatch: any) => { return (dispatch: any) => {
return new Promise<boolean>((resolve, reject) => { return new Promise<boolean>((resolve, reject) => {
dispatch(fetchListJournal(etesync)).then((journalsAction: Action<EteSync.Journal[]>) => { dispatch(fetchListJournal(etesync)).then((journalsAction: Action<EteSync.Journal[]>) => {
@ -193,7 +179,7 @@ export function fetchAll(etesync: CredentialsData, currentEntries: EntriesType)
} }
Promise.all(journals.map((journal) => { Promise.all(journals.map((journal) => {
const prevUid = currentEntries.get(journal.uid)?.value?.last(undefined)?.uid ?? null; const prevUid = currentEntries.get(journal.uid)?.last(undefined)?.uid ?? null;
// FIXME: expose it in a non-hacky way. // FIXME: expose it in a non-hacky way.
if (prevUid && (prevUid === (journal as any)._json.lastUid)) { if (prevUid && (prevUid === (journal as any)._json.lastUid)) {
@ -206,6 +192,7 @@ export function fetchAll(etesync: CredentialsData, currentEntries: EntriesType)
}; };
} }
export const addError = createAction( export const addError = createAction(
'ADD_ERRORS', 'ADD_ERRORS',
(_etesync: CredentialsData, error: Error) => { (_etesync: CredentialsData, error: 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, JournalsData, EntriesData, UserInfoData,
CredentialsDataRemote, EntriesType, SettingsType, CredentialsDataRemote, SettingsType,
fetchCount, journals, entries, credentials, userInfo, settingsReducer, encryptionKeyReducer, errorsReducer, fetchCount, journals, entries, credentials, userInfo, settingsReducer, encryptionKeyReducer, errorsReducer,
} from './reducers'; } from './reducers';
@ -19,7 +19,7 @@ export interface StoreState {
encryptionKey: {key: string}; encryptionKey: {key: string};
cache: { cache: {
journals: JournalsData; journals: JournalsData;
entries: EntriesType; entries: EntriesData;
userInfo: UserInfoData; userInfo: UserInfoData;
}; };
errors: List<Error>; errors: List<Error>;
@ -64,24 +64,24 @@ const journalsDeserialize = (state: []) => {
return newState.asImmutable(); return newState.asImmutable();
}; };
const entriesSerialize = (state: FetchType<EntriesData>) => { const entriesSerialize = (state: List<EteSync.Entry>) => {
if ((state === null) || (state.value == null)) { if (state === null) {
return null; return null;
} }
return state.value.map((x) => x.serialize()).toJS(); return state.map((x) => x.serialize()).toJS();
}; };
const entriesDeserialize = (state: EteSync.EntryJson[]): FetchType<EntriesData> => { const entriesDeserialize = (state: EteSync.EntryJson[]): List<EteSync.Entry> | null => {
if (state === null) { if (state === null) {
return new EntriesFetchRecord({ value: null }); return null;
} }
return new EntriesFetchRecord({ value: List(state.map((x: any) => { return List(state.map((x) => {
const ret = new EteSync.Entry(); const ret = new EteSync.Entry();
ret.deserialize(x); ret.deserialize(x);
return ret; return ret;
})) }); }));
}; };
const userInfoSerialize = (state: UserInfoData) => { const userInfoSerialize = (state: UserInfoData) => {
@ -102,10 +102,10 @@ const userInfoDeserialize = (state: EteSync.UserInfoJson) => {
return ret; return ret;
}; };
const cacheSerialize = (state: any, key: string) => { const cacheSerialize = (state: any, key: string | number) => {
if (key === 'entries') { if (key === 'entries') {
const ret = {}; const ret = {};
state.forEach((value: FetchType<EntriesData>, mapKey: string) => { state.forEach((value: List<EteSync.Entry>, mapKey: string) => {
ret[mapKey] = entriesSerialize(value); ret[mapKey] = entriesSerialize(value);
}); });
return ret; return ret;
@ -118,7 +118,7 @@ const cacheSerialize = (state: any, key: string) => {
return state; return state;
}; };
const cacheDeserialize = (state: any, key: string) => { const cacheDeserialize = (state: any, key: string | number) => {
if (key === 'entries') { if (key === 'entries') {
const ret = {}; const ret = {};
Object.keys(state).forEach((mapKey) => { Object.keys(state).forEach((mapKey) => {

@ -1,5 +1,5 @@
import { addEntries, fetchEntries } from './actions'; import { addEntries, fetchEntries } from './actions';
import { entries, EntriesTypeImmutable } from './reducers'; import { entries, EntriesData } from './reducers';
import { Map } from 'immutable'; import { Map } from 'immutable';
@ -7,7 +7,7 @@ import * as EteSync from 'etesync';
it('Entries reducer', () => { it('Entries reducer', () => {
const jId = '24324324324'; const jId = '24324324324';
let state = Map({}) as EntriesTypeImmutable; let state = Map({}) as EntriesData;
const entry = new EteSync.Entry(); const entry = new EteSync.Entry();
entry.deserialize({ entry.deserialize({
@ -25,26 +25,26 @@ it('Entries reducer', () => {
let entry2; let entry2;
state = entries(state, action as any); state = entries(state, action as any);
journal = state.get(jId) as any; journal = state.get(jId)!;
entry2 = journal.value.get(0); entry2 = journal.get(0)!;
expect(entry2.serialize()).toEqual(entry.serialize()); expect(entry2.serialize()).toEqual(entry.serialize());
// We replace if there's no prevUid // We replace if there's no prevUid
state = entries(state, action as any); state = entries(state, action as any);
journal = state.get(jId) as any; journal = state.get(jId)!;
entry2 = journal.value.get(0); entry2 = journal.get(0)!;
expect(entry2.serialize()).toEqual(entry.serialize()); expect(entry2.serialize()).toEqual(entry.serialize());
expect(journal.value.size).toBe(1); expect(journal.size).toBe(1);
// We extend if prevUid is set // We extend if prevUid is set
action.meta.prevUid = entry.uid; action.meta.prevUid = entry.uid;
state = entries(state, action as any); state = entries(state, action as any);
journal = state.get(jId) as any; journal = state.get(jId)!;
expect(journal.value.size).toBe(2); expect(journal.size).toBe(2);
// Creating entries should also work the same // Creating entries should also work the same
action.type = addEntries.toString(); action.type = addEntries.toString();
state = entries(state, action as any); state = entries(state, action as any);
journal = state.get(jId) as any; journal = state.get(jId)!;
expect(journal.value.size).toBe(3); expect(journal.size).toBe(3);
}); });

@ -1,18 +1,12 @@
import { Action, ActionMeta, 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 } from 'immutable';
import * as EteSync from 'etesync'; import * as EteSync from 'etesync';
import * as actions from './actions'; import * as actions from './actions';
interface FetchTypeInterface<T> {
value: T | null;
fetching?: boolean;
error?: Error;
}
export interface CredentialsDataRemote { export interface CredentialsDataRemote {
serviceApiUrl: string; serviceApiUrl: string;
credentials: EteSync.Credentials; credentials: EteSync.Credentials;
@ -22,51 +16,13 @@ export interface CredentialsData extends CredentialsDataRemote {
encryptionKey: string; encryptionKey: string;
} }
export type FetchType<T> = FetchTypeInterface<T>;
function fetchTypeRecord<T>() {
return Record<FetchTypeInterface<T>>({
value: null as T | null,
error: undefined,
});
}
export type JournalsData = ImmutableMap<string, EteSync.Journal>; export type JournalsData = ImmutableMap<string, EteSync.Journal>;
export type EntriesData = List<EteSync.Entry>; export type EntriesListData = List<EteSync.Entry>;
export const EntriesFetchRecord = fetchTypeRecord<EntriesData>(); export type EntriesData = ImmutableMap<string, EntriesListData>;
export type EntriesTypeImmutable = ImmutableMap<string, Record<FetchType<EntriesData>>>;
export type EntriesType = ImmutableMap<string, FetchType<EntriesData>>;
export type UserInfoData = EteSync.UserInfo; export type UserInfoData = EteSync.UserInfo;
function fetchTypeIdentityReducer(
state: Record<FetchType<any>> = fetchTypeRecord<any>()(), action: any, extend = false) {
if (action.error) {
return state.set('error', action.payload);
} else {
const payload = (action.payload === undefined) ? null : action.payload;
state = state.set('error', undefined);
if (action.payload === undefined) {
return state;
}
let value = state.get('value', null);
if (extend && (value !== null)) {
if (payload !== null) {
value = value.concat(payload);
}
} else if (payload !== null) {
value = List(payload);
} else {
value = null;
}
return state.set('value', value);
}
}
export const encryptionKeyReducer = handleActions( export const encryptionKeyReducer = handleActions(
{ {
[actions.deriveKey.toString()]: (_state: {key: string | null}, action: any) => ( [actions.deriveKey.toString()]: (_state: {key: string | null}, action: any) => (
@ -105,22 +61,47 @@ export const credentials = handleActions(
{} as CredentialsDataRemote {} as CredentialsDataRemote
); );
function fetchCreateEntriesReducer(state: EntriesTypeImmutable, action: any) { function entriesListSetExtend(
state: List<any> | undefined, action: Action<EteSync.Entry[]>, extend = false) {
state = state ?? List([]);
if (action.error) {
return state;
} else {
const payload = action.payload ?? null;
if (!payload) {
return state;
}
if (extend && (state !== null)) {
if (payload !== null) {
state = state.concat(payload);
}
} else if (payload !== null) {
state = List(payload);
}
return state;
}
}
function fetchCreateEntriesReducer(state: EntriesData, action: any) {
const prevState = state.get(action.meta.journal); const prevState = state.get(action.meta.journal);
const extend = action.meta.prevUid != null; const extend = action.meta.prevUid !== null;
return state.set(action.meta.journal, return state.set(action.meta.journal,
fetchTypeIdentityReducer(prevState, action, extend)); entriesListSetExtend(prevState, action, extend));
} }
export const entries = handleActions( export const entries = handleActions(
{ {
[actions.fetchEntries.toString()]: fetchCreateEntriesReducer, [actions.fetchEntries.toString()]: fetchCreateEntriesReducer,
[actions.addEntries.toString()]: fetchCreateEntriesReducer, [actions.addEntries.toString()]: fetchCreateEntriesReducer,
[actions.addJournal.toString()]: (state: EntriesTypeImmutable, action: any) => { [actions.addJournal.toString()]: (state: EntriesData, action: any) => {
const journal = action.meta.item.uid; const journal = action.meta.item.uid;
const prevState = state.get(journal); return state.set(journal, List([]));
return state.set(journal, },
fetchTypeIdentityReducer(prevState, { payload: [] }, false)); [actions.logout.toString()]: (state: EntriesData, _action: any) => {
return state.clear();
}, },
}, },
ImmutableMap({}) ImmutableMap({})

Loading…
Cancel
Save