293 lines
7.4 KiB
TypeScript
293 lines
7.4 KiB
TypeScript
// SPDX-FileCopyrightText: © 2017 EteSync Authors
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
import { Action, ActionMeta, ActionFunctionAny, combineActions, handleAction, handleActions } from 'redux-actions';
|
|
import { shallowEqual } from 'react-redux';
|
|
|
|
import { List, Map as ImmutableMap } from 'immutable';
|
|
|
|
import * as EteSync from 'etesync';
|
|
|
|
import * as actions from './actions';
|
|
|
|
export interface CredentialsDataRemote {
|
|
serviceApiUrl: string;
|
|
credentials: EteSync.Credentials;
|
|
}
|
|
|
|
export interface CredentialsData extends CredentialsDataRemote {
|
|
encryptionKey: string;
|
|
}
|
|
|
|
export type JournalsData = ImmutableMap<string, EteSync.Journal>;
|
|
|
|
export type EntriesListData = List<EteSync.Entry>;
|
|
export type EntriesData = ImmutableMap<string, EntriesListData>;
|
|
|
|
export type UserInfoData = EteSync.UserInfo;
|
|
|
|
export const encryptionKeyReducer = handleActions(
|
|
{
|
|
[actions.deriveKey.toString()]: (_state: { key: string | null }, action: any) => (
|
|
{ key: action.payload }
|
|
),
|
|
[actions.resetKey.toString()]: (_state: { key: string | null }, _action: any) => (
|
|
{ key: null }
|
|
),
|
|
[actions.logout.toString()]: (_state: { key: string | null }, _action: any) => {
|
|
return { out: true, key: null };
|
|
},
|
|
},
|
|
{ key: null }
|
|
);
|
|
|
|
export const credentials = handleActions(
|
|
{
|
|
[actions.fetchCredentials.toString()]: (
|
|
state: CredentialsDataRemote, action: any) => {
|
|
if (action.error) {
|
|
return state;
|
|
} else if (action.payload === undefined) {
|
|
return state;
|
|
} else {
|
|
const {
|
|
encryptionKey, // We don't want to set encryption key here.
|
|
...payload
|
|
} = action.payload;
|
|
return payload;
|
|
}
|
|
},
|
|
[actions.logout.toString()]: (_state: CredentialsDataRemote, _action: any) => {
|
|
return {};
|
|
},
|
|
},
|
|
{} as CredentialsDataRemote
|
|
);
|
|
|
|
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 extend = action.meta.prevUid !== null;
|
|
return state.set(action.meta.journal,
|
|
entriesListSetExtend(prevState, action, extend));
|
|
}
|
|
|
|
export const entries = handleActions(
|
|
{
|
|
[actions.fetchEntries.toString()]: fetchCreateEntriesReducer,
|
|
[actions.addEntries.toString()]: fetchCreateEntriesReducer,
|
|
[actions.addJournal.toString()]: (state: EntriesData, action: any) => {
|
|
const journal = action.meta.item.uid;
|
|
return state.set(journal, List([]));
|
|
},
|
|
[actions.logout.toString()]: (state: EntriesData, _action: any) => {
|
|
return state.clear();
|
|
},
|
|
},
|
|
ImmutableMap({})
|
|
);
|
|
|
|
const setMapModelReducer = (state: JournalsData, action: Action<EteSync.Journal[]>) => {
|
|
if (action.error || !action.payload) {
|
|
return state;
|
|
}
|
|
|
|
state = state ?? ImmutableMap<string, EteSync.Journal>().asMutable();
|
|
const old = state.asMutable();
|
|
|
|
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);
|
|
}
|
|
|
|
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 = (state: JournalsData, action: ActionMeta<EteSync.Journal, { item: EteSync.Journal }>) => {
|
|
if (action.error) {
|
|
return state;
|
|
} else {
|
|
let payload = (action.payload === undefined) ? null : action.payload;
|
|
payload = (action.meta === undefined) ? payload : action.meta.item;
|
|
|
|
if (!payload) {
|
|
return state;
|
|
}
|
|
|
|
const item = payload;
|
|
return state.set(item.uid, item);
|
|
}
|
|
};
|
|
|
|
const deleteMapModelReducer = (state: JournalsData, action: ActionMeta<EteSync.Journal, { item: EteSync.Journal }>) => {
|
|
if (action.error) {
|
|
return state;
|
|
} else {
|
|
let payload = (action.payload === undefined) ? null : action.payload;
|
|
payload = (action.meta === undefined) ? payload : action.meta.item;
|
|
|
|
if (!payload) {
|
|
return state;
|
|
}
|
|
|
|
const uid = payload.uid;
|
|
return state.delete(uid);
|
|
}
|
|
};
|
|
|
|
export const journals = handleActions(
|
|
{
|
|
[actions.fetchListJournal.toString()]: setMapModelReducer as any,
|
|
[actions.addJournal.toString()]: addEditMapModelReducer,
|
|
[actions.updateJournal.toString()]: addEditMapModelReducer,
|
|
[actions.deleteJournal.toString()]: deleteMapModelReducer,
|
|
[actions.logout.toString()]: (state: JournalsData, _action: any) => {
|
|
return state.clear();
|
|
},
|
|
},
|
|
ImmutableMap({})
|
|
);
|
|
|
|
export const userInfo = handleActions(
|
|
{
|
|
[combineActions(
|
|
actions.fetchUserInfo,
|
|
actions.createUserInfo
|
|
).toString()]: (state: UserInfoData | null, action: any) => {
|
|
if (action.error) {
|
|
return state;
|
|
} else {
|
|
let payload = action.payload ?? null;
|
|
|
|
if (payload === null) {
|
|
return state;
|
|
}
|
|
|
|
payload = action.meta?.userInfo ?? payload;
|
|
|
|
if (!state || !shallowEqual(state.serialize(), payload.serialize())) {
|
|
return payload;
|
|
}
|
|
|
|
return state;
|
|
}
|
|
},
|
|
[actions.logout.toString()]: (_state: any, _action: any) => {
|
|
return null;
|
|
},
|
|
},
|
|
null
|
|
);
|
|
|
|
const fetchActions = [
|
|
] as Array<ActionFunctionAny<Action<any>>>;
|
|
|
|
for (const func in actions) {
|
|
if (func.startsWith('fetch') ||
|
|
func.startsWith('add') ||
|
|
func.startsWith('update') ||
|
|
func.startsWith('delete')) {
|
|
|
|
fetchActions.push(actions[func]);
|
|
}
|
|
}
|
|
|
|
// Indicates network activity, not just fetch
|
|
export const fetchCount = handleAction(
|
|
combineActions(
|
|
...fetchActions
|
|
),
|
|
(state: number, action: any) => {
|
|
if (action.payload === undefined) {
|
|
return state + 1;
|
|
} else {
|
|
return state - 1;
|
|
}
|
|
},
|
|
0
|
|
);
|
|
|
|
export const errorsReducer = handleActions(
|
|
{
|
|
[combineActions(
|
|
actions.fetchListJournal,
|
|
actions.addJournal,
|
|
actions.updateJournal,
|
|
actions.deleteJournal,
|
|
actions.fetchEntries,
|
|
actions.addEntries
|
|
).toString()]: (state: List<Error>, action: Action<any>) => {
|
|
if (action.error) {
|
|
return state.push(action.payload);
|
|
}
|
|
|
|
return state;
|
|
},
|
|
[actions.addError.toString()]: (state: List<Error>, action: Action<any>) => {
|
|
return state.push(action.payload);
|
|
},
|
|
[actions.clearErros.toString()]: (state: List<Error>, _action: Action<any>) => {
|
|
return state.clear();
|
|
},
|
|
},
|
|
List([])
|
|
);
|
|
|
|
|
|
// FIXME Move all the below (potentially the fetchCount ones too) to their own file
|
|
export interface SettingsType {
|
|
locale: string;
|
|
taskSettings: {
|
|
filterBy: string | null;
|
|
};
|
|
}
|
|
|
|
export const settingsReducer = handleActions(
|
|
{
|
|
[actions.setSettings.toString()]: (state: { key: string | null }, action: any) => (
|
|
{ ...state, ...action.payload }
|
|
),
|
|
},
|
|
{
|
|
locale: 'en-gb',
|
|
taskSettings: {
|
|
filterBy: null,
|
|
},
|
|
}
|
|
);
|