diff --git a/src/Journal.tsx b/src/Journal.tsx
index e7cd9ef..da434b2 100644
--- a/src/Journal.tsx
+++ b/src/Journal.tsx
@@ -39,7 +39,7 @@ class Journal extends React.Component {
render() {
const journalUid = this.props.match.params.journalUid;
- const entries = this.props.entries[journalUid];
+ const entries = this.props.entries.get(journalUid);
if ((!entries) || (entries.value === null)) {
return ();
diff --git a/src/JournalEntries.tsx b/src/JournalEntries.tsx
index f33c64b..5170bfd 100644
--- a/src/JournalEntries.tsx
+++ b/src/JournalEntries.tsx
@@ -1,3 +1,5 @@
+import * as Immutable from 'immutable';
+
import * as React from 'react';
import { List, ListItem } from 'material-ui/List';
import Dialog from 'material-ui/Dialog';
@@ -23,7 +25,7 @@ class JournalEntries extends React.Component {
props: {
journal: EteSync.Journal,
- entries: Array,
+ entries: Immutable.List,
};
constructor(props: any) {
diff --git a/src/Pim.tsx b/src/Pim.tsx
index ed77426..83d1b2c 100644
--- a/src/Pim.tsx
+++ b/src/Pim.tsx
@@ -49,7 +49,11 @@ class Pim extends React.Component {
return;
}
- const entries = this.props.entries[journal.uid];
+ const entries = this.props.entries.get(journal.uid);
+
+ if (!entries) {
+ return;
+ }
if (entries.value === null) {
return;
@@ -91,7 +95,7 @@ class Pim extends React.Component {
let collectionsCalendar: Array = [];
const journalMap = this.props.journals.reduce(
(ret, journal) => {
- const journalEntries = this.props.entries[journal.uid];
+ const journalEntries = this.props.entries.get(journal.uid);
const cryptoManager = new EteSync.CryptoManager(derived, journal.uid, journal.version);
let prevUid: string | null = null;
diff --git a/src/SyncGate.tsx b/src/SyncGate.tsx
index b5748f3..ea2eb39 100644
--- a/src/SyncGate.tsx
+++ b/src/SyncGate.tsx
@@ -35,9 +35,10 @@ class SyncGate extends React.Component {
if (nextProps.journals.value && (this.props.journals.value !== nextProps.journals.value)) {
for (const journal of nextProps.journals.value) {
let prevUid: string | null = null;
- const entries = this.props.entries[journal.uid];
- if (entries && entries.value && (entries.value.length > 0)) {
- prevUid = entries.value[entries.value.length - 1].uid;
+ const entries = this.props.entries.get(journal.uid);
+ if (entries && entries.value) {
+ const last = entries.value.last();
+ prevUid = (last) ? last.uid : null;
}
store.dispatch(fetchEntries(this.props.etesync, journal.uid, prevUid));
@@ -46,14 +47,11 @@ class SyncGate extends React.Component {
}
render() {
- const entryArrays = Object.keys(this.props.entries).map((key) => {
- return this.props.entries[key].value;
- });
-
+ const entryArrays = this.props.entries;
const journals = this.props.journals.value;
if ((journals === null) ||
- (entryArrays.length === 0) || !entryArrays.every((x: any) => (x !== null))) {
+ (entryArrays.size === 0) || !entryArrays.every((x: any) => (x.value !== null))) {
return ();
}
diff --git a/src/etesync-helpers.tsx b/src/etesync-helpers.tsx
index 17384ac..c3dd3be 100644
--- a/src/etesync-helpers.tsx
+++ b/src/etesync-helpers.tsx
@@ -1,3 +1,5 @@
+import { List } from 'immutable';
+
import * as EteSync from './api/EteSync';
import { CredentialsData, createEntries } from './store';
@@ -5,7 +7,7 @@ import { CredentialsData, createEntries } from './store';
export function createJournalEntry(
etesync: CredentialsData,
journal: EteSync.Journal,
- existingEntries: Array,
+ existingEntries: List,
action: EteSync.SyncEntryAction,
content: string) {
@@ -18,8 +20,9 @@ export function createJournalEntry(
let prevUid: string | null = null;
const entries = existingEntries;
- if (entries.length > 0) {
- prevUid = entries[entries.length - 1].uid;
+ const last = entries.last();
+ if (last) {
+ prevUid = last.uid;
}
const cryptoManager = new EteSync.CryptoManager(derived, journal.uid, journal.version);
diff --git a/src/journal-processors.tsx b/src/journal-processors.tsx
index 8c47c8a..3b3d6a9 100644
--- a/src/journal-processors.tsx
+++ b/src/journal-processors.tsx
@@ -1,10 +1,12 @@
+import { List } from 'immutable';
+
import * as ICAL from 'ical.js';
import { EventType, ContactType } from './pim-types';
import * as EteSync from './api/EteSync';
-export function syncEntriesToItemMap(collection: EteSync.CollectionInfo, entries: EteSync.SyncEntry[]) {
+export function syncEntriesToItemMap(collection: EteSync.CollectionInfo, entries: List) {
let items: {[key: string]: ContactType} = {};
for (const syncEntry of entries) {
@@ -47,7 +49,7 @@ function colorIntToHtml(color: number) {
((alpha > 0) ? toHex(alpha) : '');
}
-export function syncEntriesToCalendarItemMap(collection: EteSync.CollectionInfo, entries: EteSync.SyncEntry[]) {
+export function syncEntriesToCalendarItemMap(collection: EteSync.CollectionInfo, entries: List) {
let items: {[key: string]: EventType} = {};
const color = colorIntToHtml(collection.color);
diff --git a/src/store.test.tsx b/src/store.test.tsx
index c6a0e43..bc88d1a 100644
--- a/src/store.test.tsx
+++ b/src/store.test.tsx
@@ -1,10 +1,12 @@
-import { entries, createEntries, fetchEntries } from './store';
+import { entries, createEntries, fetchEntries, EntriesTypeImmutable } from './store';
+
+import { Map } from 'immutable';
import * as EteSync from './api/EteSync';
it('Entries reducer', () => {
const jId = '24324324324';
- let state = {};
+ let state = Map({}) as EntriesTypeImmutable;
let entry = new EteSync.Entry();
entry.deserialize({
@@ -18,21 +20,30 @@ it('Entries reducer', () => {
payload: [entry],
};
+ let journal;
+ let entry2;
+
state = entries(state, action as any);
- expect(state[jId].value[0].serialize()).toEqual(entry.serialize());
+ journal = state.get(jId) as any;
+ entry2 = journal.value.get(0);
+ expect(entry2.serialize()).toEqual(entry.serialize());
// We replace if there's no prevUid
state = entries(state, action as any);
- expect(state[jId].value[0].serialize()).toEqual(entry.serialize());
- expect(state[jId].value.length).toBe(1);
+ journal = state.get(jId) as any;
+ entry2 = journal.value.get(0);
+ expect(entry2.serialize()).toEqual(entry.serialize());
+ expect(journal.value.size).toBe(1);
// We extend if prevUid is set
action.meta.prevUid = entry.uid;
state = entries(state, action as any);
- expect(state[jId].value.length).toBe(2);
+ journal = state.get(jId) as any;
+ expect(journal.value.size).toBe(2);
// Creating entries should also work the same
action.type = createEntries.toString();
state = entries(state, action as any);
- expect(state[jId].value.length).toBe(3);
+ journal = state.get(jId) as any;
+ expect(journal.value.size).toBe(3);
});
diff --git a/src/store.tsx b/src/store.tsx
index 3b67d95..c28c9a7 100644
--- a/src/store.tsx
+++ b/src/store.tsx
@@ -6,11 +6,13 @@ import session from 'redux-persist/lib/storage/session';
import thunkMiddleware from 'redux-thunk';
import { createLogger } from 'redux-logger';
+import { List, Map, Record } from 'immutable';
+
import promiseMiddleware from './promise-middleware';
import * as EteSync from './api/EteSync';
-export interface FetchType {
+interface FetchTypeInterface {
value: T | null;
fetching?: boolean;
error?: Error;
@@ -22,15 +24,27 @@ export interface CredentialsData {
encryptionKey: string;
}
+type FetchType = FetchTypeInterface;
+
+function fetchTypeRecord() {
+ return Record>({
+ value: null as T | null,
+ });
+}
+
export type CredentialsType = FetchType;
-export type JournalsData = Array;
+export type JournalsData = List;
+const JournalsFetchRecord = fetchTypeRecord();
export type JournalsType = FetchType;
-export type EntriesData = Array;
+export type EntriesData = List;
-export type EntriesType = {[key: string]: FetchType};
+const EntriesFetchRecord = fetchTypeRecord();
+
+export type EntriesTypeImmutable = Map>>;
+export type EntriesType = Map>;
export interface StoreState {
fetchCount: number;
@@ -41,7 +55,7 @@ export interface StoreState {
};
}
-function fetchTypeIdentityReducer(state: FetchType = {value: null}, action: any, extend: boolean = false) {
+function credentialsIdentityReducer(state: CredentialsType = {value: null}, action: any, extend: boolean = false) {
if (action.error) {
return {
value: null,
@@ -50,18 +64,32 @@ function fetchTypeIdentityReducer(state: FetchType = {value: null}, action:
} else {
const fetching = (action.payload === undefined) ? true : undefined;
const payload = (action.payload === undefined) ? null : action.payload;
- let value = state.value;
+ let value = payload;
+ return {
+ fetching,
+ value,
+ };
+ }
+}
+
+function fetchTypeIdentityReducer(
+ state: Record> = fetchTypeRecord()(), action: any, extend: boolean = false) {
+ if (action.error) {
+ return state.set('value', null).set('error', action.payload);
+ } else {
+ const fetching = (action.payload === undefined) ? true : undefined;
+ const payload = (action.payload === undefined) ? null : action.payload;
+ 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 = payload;
+ value = null;
}
- return {
- fetching,
- value,
- };
+ return state.set('value', value).set('fetching', fetching);
}
}
@@ -131,7 +159,7 @@ export const { fetchEntries, createEntries } = createActions({
const credentials = handleActions(
{
- [fetchCredentials.toString()]: fetchTypeIdentityReducer,
+ [fetchCredentials.toString()]: credentialsIdentityReducer,
[logout.toString()]: (state: CredentialsType, action: any) => {
return {out: true, value: null};
},
@@ -141,20 +169,19 @@ const credentials = handleActions(
export const entries = handleAction(
combineActions(fetchEntries, createEntries),
- (state: EntriesType, action: any) => {
- const prevState = state[action.meta.journal];
+ (state: EntriesTypeImmutable, action: any) => {
+ const prevState = state.get(action.meta.journal);
const extend = action.meta.prevUid != null;
- return { ...state,
- [action.meta.journal]: fetchTypeIdentityReducer(prevState, action, extend)
- };
+ return state.set(action.meta.journal,
+ fetchTypeIdentityReducer(prevState, action, extend));
},
- {}
+ Map({})
);
const journals = handleAction(
fetchJournals,
fetchTypeIdentityReducer,
- {value: null}
+ JournalsFetchRecord(),
);
const fetchCount = handleAction(
@@ -184,7 +211,7 @@ const journalsSerialize = (state: JournalsData) => {
return null;
}
- return state.map((x) => x.serialize());
+ return state.map((x) => x.serialize()).toJS();
};
const journalsDeserialize = (state: EteSync.JournalJson[]) => {
@@ -192,53 +219,74 @@ const journalsDeserialize = (state: EteSync.JournalJson[]) => {
return null;
}
- return state.map((x: any) => {
+ return List(state.map((x: any) => {
let ret = new EteSync.Journal(x.version);
ret.deserialize(x);
return ret;
- });
-};
-
-const cacheJournalsPersistConfig = {
- key: 'journals',
- storage: localforage,
- transforms: [createTransform(journalsSerialize, journalsDeserialize)],
- whitelist: ['value'],
+ }));
};
-const entriesSerialize = (state: FetchType, key: string) => {
+const entriesSerialize = (state: FetchType) => {
if ((state === null) || (state.value == null)) {
return null;
}
- return state.value.map((x) => x.serialize());
+ return state.value.map((x) => x.serialize()).toJS();
};
-const entriesDeserialize = (state: EteSync.EntryJson[], key: string): FetchType => {
+const entriesDeserialize = (state: EteSync.EntryJson[]): FetchType => {
if (state === null) {
- return {value: null};
+ return EntriesFetchRecord({value: null});
}
- return {value: state.map((x: any) => {
+ return EntriesFetchRecord({value: List(state.map((x: any) => {
let ret = new EteSync.Entry();
ret.deserialize(x);
return ret;
- })};
+ }))});
+};
+
+const cacheSerialize = (state: any, key: string) => {
+ if (key === 'entries') {
+ let ret = {};
+ state.forEach((value: FetchType, mapKey: string) => {
+ ret[mapKey] = entriesSerialize(value);
+ });
+ return ret;
+ } else if (key === 'journals') {
+ return journalsSerialize(state.value);
+ }
+
+ return state;
+};
+
+const cacheDeserialize = (state: any, key: string) => {
+ if (key === 'entries') {
+ let ret = {};
+ Object.keys(state).forEach((mapKey) => {
+ ret[mapKey] = entriesDeserialize(state[mapKey]);
+ });
+ return Map(ret);
+ } else if (key === 'journals') {
+ return JournalsFetchRecord({value: journalsDeserialize(state)});
+ }
+
+ return state;
};
-const cacheEntriesPersistConfig = {
- key: 'entries',
+const cachePersistConfig = {
+ key: 'cache',
storage: localforage,
- transforms: [createTransform(entriesSerialize, entriesDeserialize)],
+ transforms: [createTransform(cacheSerialize, cacheDeserialize)],
};
const reducers = combineReducers({
fetchCount,
credentials: persistReducer(credentialsPersistConfig, credentials),
- cache: combineReducers({
- journals: persistReducer(cacheJournalsPersistConfig, journals),
- entries: persistReducer(cacheEntriesPersistConfig, entries),
- })
+ cache: persistReducer(cachePersistConfig, combineReducers({
+ entries,
+ journals,
+ })),
});
let middleware = [