convert to new patterns for state service.

This commit is contained in:
Jason Wall 2024-03-08 11:39:45 -05:00
parent 6f1cd2c68a
commit 94bc54d06b
8 changed files with 973 additions and 1151 deletions

View File

@ -128,3 +128,9 @@ export function createStateService<TState, TAction extends { type: string }>(
return stateServiceClass as IfImmutable<TState, typeof stateServiceClass, YourStateTypeNeedsToBeImmutable<TState>>;
}
export interface IStateAction<TState, TAction> {
type: TAction;
handle: (state: TState) => TState;
}

View File

@ -22,5 +22,5 @@ export interface UserVersion {
export enum StorableType {
initial,
modified
modified,
}

View File

@ -1,297 +0,0 @@
import { Error, User, Settings } from '../models/app-state';
import { IStorable } from '../common/storable';
import { NoteItem } from '../models/note-state';
import { MoveDirection } from '../common/move-direction';
import { SavedPage } from '../models/page-state';
import { CardItem } from '../models/card-state';
import { Overlap } from '../common/bible-reference';
export class AppActionFactory {
static newSavePage(title: string): AppAction {
return {
type: 'SAVE_PAGE',
title,
} as AppAction;
}
static newUpdateCurrentPage(): AppAction {
return {
type: 'UPDATE_CURRENT_PAGE',
} as AppAction;
}
static newUpdateSavedPages(savedPages: IStorable<readonly SavedPage[]>): AppAction {
return {
type: 'UPDATE_SAVED_PAGES',
savedPages,
} as AppAction;
}
static newUpdateSavedPage(savedPage: SavedPage): AppAction {
return {
type: 'UPDATE_SAVED_PAGE',
savedPage,
} as AppAction;
}
static newMoveSavedPageCard(savedPage: SavedPage, fromIndex: number, toIndex: number): AppAction {
return {
type: 'MOVE_SAVED_PAGE_CARD',
savedPage,
fromIndex,
toIndex,
} as AppAction;
}
static newRemoveSavedPage(savedPage: SavedPage): AppAction {
return {
type: 'REMOVE_SAVED_PAGE',
savedPage,
} as AppAction;
}
static newAddCardToSavedPage(card: CardItem, pageId: string): AppAction {
return {
type: 'ADD_CARD_TO_SAVED_PAGE',
card,
pageId,
} as AppAction;
}
static newAddCard(card: CardItem, nextToItem: CardItem): AppAction {
return {
type: 'ADD_CARD',
card,
nextToItem,
} as AppAction;
}
static newUpdateCard(newCard: CardItem, oldCard: CardItem): AppAction {
return {
type: 'UPDATE_CARD',
newCard,
oldCard,
} as AppAction;
}
static newRemoveCard(card: CardItem): AppAction {
return {
type: 'REMOVE_CARD',
card,
} as AppAction;
}
static newMoveCard(card: CardItem, direction: MoveDirection): AppAction {
return {
type: 'MOVE_CARD',
card,
direction,
} as AppAction;
}
static newUpdateCards(cards: IStorable<CardItem[]>): AppAction {
return {
type: 'UPDATE_CARDS',
cards,
} as AppAction;
}
static newClearCards(): AppAction {
return {
type: 'CLEAR_CARDS',
} as AppAction;
}
static newUpdateError(error: Error): AppAction {
return {
type: 'UPDATE_ERROR',
error,
} as AppAction;
}
static newUpdateCardMergeStrategy(strategy: Overlap): AppAction {
return {
type: 'UPDATE_CARD_MERGE_STRATEGY',
cardMergeStrategy: strategy,
};
}
static newUpdateCardFontSize(cardFontSize: number): AppAction {
return {
type: 'UPDATE_CARD_FONT_SIZE',
cardFontSize,
} as AppAction;
}
static newUpdateCardFontFamily(cardFontFamily: string): AppAction {
return {
type: 'UPDATE_CARD_FONT_FAMILY',
cardFontFamily,
} as AppAction;
}
static newUpdateAutocomplete(words: string[]): AppAction {
return {
type: 'UPDATE_AUTOCOMPLETE',
words,
} as AppAction;
}
static newUpdateSettings(settings: IStorable<Settings>): AppAction {
return {
type: 'UPDATE_SETTINGS',
settings,
} as AppAction;
}
static newUser(user: User): AppAction {
return {
type: 'SET_USER',
user,
} as AppAction;
}
static newFindNotes(qry: string, nextToItem: CardItem): AppAction {
return {
type: 'FIND_NOTES',
qry,
nextToItem,
} as AppAction;
}
static newGetNote(noteId: string, nextToItem: CardItem): AppAction {
return {
type: 'GET_NOTE',
noteId,
nextToItem,
} as AppAction;
}
static newUpdateNotes(notes: IStorable<readonly NoteItem[]>): AppAction {
return {
type: 'UPDATE_NOTES',
notes,
} as AppAction;
}
static newSaveNote(note: NoteItem): AppAction {
return {
type: 'SAVE_NOTE',
note,
} as AppAction;
}
static newDeleteNote(note: NoteItem): AppAction {
return {
type: 'DELETE_NOTE',
note,
} as AppAction;
}
}
export type AppAction =
| {
type: 'SAVE_PAGE';
title: string;
}
| {
type: 'UPDATE_CURRENT_PAGE';
}
| {
type: 'UPDATE_SAVED_PAGES';
savedPages: IStorable<readonly SavedPage[]>;
}
| {
type: 'UPDATE_SAVED_PAGE';
savedPage: SavedPage;
}
| {
type: 'REMOVE_SAVED_PAGE';
savedPage: SavedPage;
}
| {
type: 'MOVE_SAVED_PAGE_CARD';
fromIndex: number;
toIndex: number;
savedPage: SavedPage;
}
| {
type: 'ADD_CARD_TO_SAVED_PAGE';
card: CardItem;
pageId: string;
}
| {
type: 'ADD_CARD';
card: CardItem;
nextToItem: CardItem;
}
| {
type: 'UPDATE_CARD';
newCard: CardItem;
oldCard: CardItem;
}
| {
type: 'REMOVE_CARD';
card: CardItem;
}
| {
type: 'CLEAR_CARDS';
}
| {
type: 'MOVE_CARD';
card: CardItem;
direction: MoveDirection;
}
| {
type: 'UPDATE_CARDS';
cards: IStorable<readonly CardItem[]>;
}
| {
type: 'UPDATE_ERROR';
error: Error;
}
| {
type: 'UPDATE_CARD_MERGE_STRATEGY';
cardMergeStrategy: Overlap;
}
| {
type: 'UPDATE_CARD_FONT_SIZE';
cardFontSize: number;
}
| {
type: 'UPDATE_CARD_FONT_FAMILY';
cardFontFamily: string;
}
| {
type: 'UPDATE_AUTOCOMPLETE';
words: string[];
}
| {
type: 'UPDATE_SETTINGS';
settings: IStorable<Settings>;
}
| {
type: 'SET_USER';
user: User;
}
| {
type: 'FIND_NOTES';
qry: string;
nextToItem: CardItem;
}
| {
type: 'GET_NOTE';
noteId: string;
nextToItem: CardItem;
}
| {
type: 'UPDATE_NOTES';
notes: IStorable<readonly NoteItem[]>;
}
| {
type: 'SAVE_NOTE';
note: NoteItem;
}
| {
type: 'DELETE_NOTE';
note: NoteItem;
};

View File

@ -1,52 +0,0 @@
import { AppState } from '../models/app-state';
import { Overlap } from '../common/bible-reference';
import { StorableType } from '../common/storable';
export const initialState: AppState = {
user: null,
currentCards: {
type: StorableType.initial,
createdOn: new Date(0).toISOString(),
value: [],
},
cardCache: {},
autocomplete: [],
currentSavedPage: null,
savedPages: null,
notes: {
type: StorableType.initial,
createdOn: new Date(0).toISOString(),
value: [],
},
savedPagesLoaded: false,
error: null,
settings: {
type: StorableType.initial,
createdOn: new Date(0).toISOString(),
value: {
displaySettings: {
showStrongsAsModal: false,
appendCardToBottom: true,
insertCardNextToItem: true,
clearSearchAfterQuery: true,
cardFontSize: 12,
cardFontFamily: 'PT Serif',
showVersesOnNewLine: false,
showVerseNumbers: false,
showParagraphs: true,
showParagraphHeadings: true,
syncCardsAcrossDevices: false,
},
pageSettings: {
mergeStrategy: Overlap.Equal,
},
cardIcons: {
words: 'font_download',
passage: 'menu_book',
strongs: 'speaker_notes',
note: 'note',
savedPage: 'inbox',
},
},
},
};

View File

@ -1,6 +1,4 @@
import { TestBed } from '@angular/core/testing';
import { reducer, getNewestStorable } from './app-state-reducer';
import { AppActionFactory } from './app-state-actions';
import { Overlap } from '../common/bible-reference';
import { Storable, StorableType } from '../common/storable';
import { CardType, CardItem } from '../models/card-state';
@ -9,6 +7,30 @@ import { AppState, User } from '../models/app-state';
import { getCardCacheKey } from '../common/card-cache-operations';
import { MoveDirection } from '../common/move-direction';
import { NoteItem } from '../models/note-state';
import {
addCardAction,
addCardToSavedPageAction,
deleteNoteAction,
findNotesAction,
getNewestStorable,
getNotesAction,
moveCardAction,
moveSavedPageCardAction,
removeCardAction,
removeSavedPageAction,
saveNoteAction,
savePageAction,
updateAutoCompleteAction,
updateCardAction,
updateCardFontFamilyAction,
updateCardFontSizeAction,
updateCardMergeStrategyAction,
updateCurrentPageAction,
updateNotesAction,
updateSavedPageAction,
updateSavedPagesAction,
updateSettingsAction,
} from './app.service';
describe('getNewestStorable', () => {
it('maybeMutateStorable', () => {
@ -126,8 +148,7 @@ describe('AppService Reducer', () => {
it('UPDATE_CARD_MERGE_STRATEGY', () => {
for (const strategy of [Overlap.None, Overlap.Equal, Overlap.Subset, Overlap.Intersect]) {
const action = AppActionFactory.newUpdateCardMergeStrategy(strategy);
const testState = reducer(preState, action);
const testState = updateCardMergeStrategyAction(strategy).handle(preState);
expect(testState.settings.value.pageSettings.mergeStrategy).toEqual(strategy);
}
});
@ -163,20 +184,17 @@ describe('AppService Reducer', () => {
},
};
const action = AppActionFactory.newUpdateSettings(settings);
const testState = reducer(preState, action);
const testState = updateSettingsAction(settings).handle(preState);
expect(testState.settings).toBe(settings, 'Failed to update the display settings');
});
it('UPDATE_CARD_FONT_SIZE', () => {
const action = AppActionFactory.newUpdateCardFontSize(32);
const testState = reducer(preState, action);
const testState = updateCardFontSizeAction(32).handle(preState);
expect(testState.settings.value.displaySettings.cardFontSize).toBe(32, 'Failed to change card font size to 32');
});
it('UPDATE_CARD_FONT_FAMILY', () => {
const action = AppActionFactory.newUpdateCardFontFamily('Jason');
const testState = reducer(preState, action);
const testState = updateCardFontFamilyAction('Jason').handle(preState);
expect(testState.settings.value.displaySettings.cardFontFamily).toBe(
'Jason',
'Failed to change card font family to "Jason"'
@ -188,8 +206,7 @@ describe('AppService Reducer', () => {
it('UPDATE_AUTOCOMPLETE', () => {
const words = ['word1', 'word2', 'word3'];
const action = AppActionFactory.newUpdateAutocomplete(words);
const testState = reducer(preState, action);
const testState = updateAutoCompleteAction(words).handle(preState);
expect(testState.autocomplete).toEqual(words, 'Failed to update the autocomplete array');
});
@ -210,8 +227,7 @@ describe('AppService Reducer', () => {
},
]);
const action = AppActionFactory.newUpdateSavedPages(savedPages);
const testState = reducer(preState, action);
const testState = updateSavedPagesAction(savedPages).handle(preState);
expect(testState.savedPages).toBe(savedPages, 'Failed to update the savedPages array');
expect(testState.savedPages.value.length).toBe(1, 'Updated savedPages is the wrong length');
expect(testState.savedPages.value[0].queries.length).toBe(
@ -227,8 +243,7 @@ describe('AppService Reducer', () => {
id: 'myid2',
};
const action = AppActionFactory.newUpdateSavedPage(sp);
const testState = reducer(preState, action);
const testState = updateSavedPageAction(sp).handle(preState);
expect(testState.savedPages.value[0].queries.length).toBe(
1,
@ -244,8 +259,7 @@ describe('AppService Reducer', () => {
it('REMOVE_SAVED_PAGE', () => {
const sp = preState.savedPages.value[0];
const action = AppActionFactory.newRemoveSavedPage(sp);
const testState = reducer(preState, action);
const testState = removeSavedPageAction(sp).handle(preState);
expect(testState.savedPages.value.length).toBe(1, 'Updated savedPages should be 1');
expect(testState.savedPages.value[0].title).toBe('page2');
@ -258,12 +272,10 @@ describe('AppService Reducer', () => {
type: CardType.Strongs,
};
const action1 = AppActionFactory.newAddCard(card1, null);
const testState = reducer(preState, action1);
const testState = addCardAction(card1, null).handle(preState);
expect(testState.currentCards.value[0]).toBe(card1, 'Failed to add first card to empty list');
const action = AppActionFactory.newUpdateCurrentPage();
const testState2 = reducer(testState, action);
const testState2 = updateCurrentPageAction().handle(testState);
expect(testState2.currentSavedPage.queries.length).toBe(1);
expect(preState.currentSavedPage.queries.length).toBe(0);
@ -276,11 +288,9 @@ describe('AppService Reducer', () => {
type: CardType.Passage,
};
const action1 = AppActionFactory.newAddCard(card1, null);
const preState2 = reducer(preState, action1);
const preState2 = addCardAction(card1, null).handle(preState);
const action = AppActionFactory.newSavePage('my saved page');
const testState = reducer(preState2, action);
const testState = savePageAction('my saved page').handle(preState2);
expect(testState.savedPages.value[2].queries.length).toBe(
1,
@ -292,8 +302,7 @@ describe('AppService Reducer', () => {
it('MOVE_SAVED_PAGE_CARD', () => {
const page = preState.savedPages.value[1];
const action1 = AppActionFactory.newMoveSavedPageCard(page, 1, 0);
const testState = reducer(preState, action1);
const testState = moveSavedPageCardAction(page, 1, 0).handle(preState);
expect(testState.savedPages.value[1].queries[0].qry).toBe('G1', 'Failed to move card in saved page');
});
@ -303,8 +312,7 @@ describe('AppService Reducer', () => {
data: null,
type: CardType.Passage,
};
const action1 = AppActionFactory.newAddCardToSavedPage(card1, 'myid1');
const testState = reducer(preState, action1);
const testState = addCardToSavedPageAction(card1, 'myid1').handle(preState);
expect(testState.savedPages.value[0].queries[1].qry).toBe('jn 3:16', 'Failed to add card to saved page');
});
@ -319,8 +327,7 @@ describe('AppService Reducer', () => {
type: CardType.Strongs,
};
const action1 = AppActionFactory.newAddCard(card1, null);
const testState = reducer(preState, action1);
const testState = addCardAction(card1, null).handle(preState);
expect(testState.currentCards.value[0]).toBe(card1, 'Failed to add first card to empty list');
const card2: CardItem = {
@ -329,8 +336,7 @@ describe('AppService Reducer', () => {
type: CardType.Strongs,
};
const action2 = AppActionFactory.newAddCard(card2, null);
const testState2 = reducer(testState, action2);
const testState2 = addCardAction(card2, null).handle(testState);
expect(testState2.currentCards.value.length).toBe(2, 'Failed to add second card to list with 1 item');
expect(testState2.currentCards.value[1]).toBe(card2);
@ -342,125 +348,109 @@ describe('AppService Reducer', () => {
// append to top, insert next to card
let setState = reducer(
testState2,
AppActionFactory.newUpdateSettings({
createdOn: new Date(2020, 1, 1, 0, 0, 0, 0).toISOString(),
type: StorableType.initial,
value: {
...testState2.settings.value,
displaySettings: {
showStrongsAsModal: true,
appendCardToBottom: false,
insertCardNextToItem: true,
clearSearchAfterQuery: false,
cardFontSize: 100,
cardFontFamily: 'Jason',
showVersesOnNewLine: true,
showVerseNumbers: true,
showParagraphs: false,
showParagraphHeadings: false,
syncCardsAcrossDevices: true,
},
let setState = updateSettingsAction({
createdOn: new Date(2020, 1, 1, 0, 0, 0, 0).toISOString(),
type: StorableType.initial,
value: {
...testState2.settings.value,
displaySettings: {
showStrongsAsModal: true,
appendCardToBottom: false,
insertCardNextToItem: true,
clearSearchAfterQuery: false,
cardFontSize: 100,
cardFontFamily: 'Jason',
showVersesOnNewLine: true,
showVerseNumbers: true,
showParagraphs: false,
showParagraphHeadings: false,
syncCardsAcrossDevices: true,
},
})
);
},
}).handle(testState2);
const action3 = AppActionFactory.newAddCard(card3, card2);
const testState3 = reducer(setState, action3);
const testState3 = addCardAction(card3, card2).handle(setState);
expect(testState3.currentCards.value.length).toBe(3, 'Failed to add third card');
expect(testState3.currentCards.value[1]).toBe(card3, 'Failed to insert card above the second card');
// append to bottom, insert next to card
setState = reducer(
testState2,
AppActionFactory.newUpdateSettings({
createdOn: new Date(2020, 1, 1, 0, 0, 0, 0).toISOString(),
type: StorableType.initial,
value: {
...testState2.settings.value,
displaySettings: {
showStrongsAsModal: true,
appendCardToBottom: true,
insertCardNextToItem: true,
clearSearchAfterQuery: false,
cardFontSize: 100,
cardFontFamily: 'Jason',
showVersesOnNewLine: true,
showVerseNumbers: true,
showParagraphs: false,
showParagraphHeadings: false,
syncCardsAcrossDevices: true,
},
setState = updateSettingsAction({
createdOn: new Date(2020, 1, 1, 0, 0, 0, 0).toISOString(),
type: StorableType.initial,
value: {
...testState2.settings.value,
displaySettings: {
showStrongsAsModal: true,
appendCardToBottom: true,
insertCardNextToItem: true,
clearSearchAfterQuery: false,
cardFontSize: 100,
cardFontFamily: 'Jason',
showVersesOnNewLine: true,
showVerseNumbers: true,
showParagraphs: false,
showParagraphHeadings: false,
syncCardsAcrossDevices: true,
},
})
);
},
}).handle(testState2);
const action4 = AppActionFactory.newAddCard(card3, card1);
const testState4 = reducer(setState, action4);
const testState4 = addCardAction(card3, card1).handle(setState);
expect(testState4.currentCards.value.length).toBe(3, 'Failed to add third card');
expect(testState4.currentCards.value[1]).toBe(card3, 'Failed to insert card below the first card');
// append to bottom, do not insert next to card
setState = reducer(
testState2,
AppActionFactory.newUpdateSettings({
createdOn: new Date(2020, 1, 1, 0, 0, 0, 0).toISOString(),
type: StorableType.initial,
value: {
...testState2.settings.value,
displaySettings: {
showStrongsAsModal: true,
appendCardToBottom: true,
insertCardNextToItem: false,
clearSearchAfterQuery: false,
cardFontSize: 100,
cardFontFamily: 'Jason',
showVersesOnNewLine: true,
showVerseNumbers: true,
showParagraphs: false,
showParagraphHeadings: false,
syncCardsAcrossDevices: true,
},
setState = updateSettingsAction({
createdOn: new Date(2020, 1, 1, 0, 0, 0, 0).toISOString(),
type: StorableType.initial,
value: {
...testState2.settings.value,
displaySettings: {
showStrongsAsModal: true,
appendCardToBottom: true,
insertCardNextToItem: false,
clearSearchAfterQuery: false,
cardFontSize: 100,
cardFontFamily: 'Jason',
showVersesOnNewLine: true,
showVerseNumbers: true,
showParagraphs: false,
showParagraphHeadings: false,
syncCardsAcrossDevices: true,
},
})
);
},
}).handle(testState2);
const action5 = AppActionFactory.newAddCard(card3, card1);
const testState5 = reducer(setState, action5);
const testState5 = addCardAction(card3, card1).handle(setState);
expect(testState5.currentCards.value.length).toBe(3, 'Failed to add third card');
expect(testState5.currentCards.value[2]).toBe(card3, 'Failed to insert card at end of the list');
// append to top, do not insert next to card
setState = reducer(
testState2,
AppActionFactory.newUpdateSettings({
createdOn: new Date(2020, 1, 1, 0, 0, 0, 0).toISOString(),
type: StorableType.initial,
value: {
...testState2.settings.value,
displaySettings: {
showStrongsAsModal: true,
appendCardToBottom: false,
insertCardNextToItem: false,
clearSearchAfterQuery: false,
cardFontSize: 100,
cardFontFamily: 'Jason',
showVersesOnNewLine: true,
showVerseNumbers: true,
showParagraphs: false,
showParagraphHeadings: false,
syncCardsAcrossDevices: true,
},
setState = updateSettingsAction({
createdOn: new Date(2020, 1, 1, 0, 0, 0, 0).toISOString(),
type: StorableType.initial,
value: {
...testState2.settings.value,
displaySettings: {
showStrongsAsModal: true,
appendCardToBottom: false,
insertCardNextToItem: false,
clearSearchAfterQuery: false,
cardFontSize: 100,
cardFontFamily: 'Jason',
showVersesOnNewLine: true,
showVerseNumbers: true,
showParagraphs: false,
showParagraphHeadings: false,
syncCardsAcrossDevices: true,
},
})
);
},
}).handle(testState2);
const action6 = AppActionFactory.newAddCard(card3, card1);
const testState6 = reducer(setState, action6);
const testState6 = addCardAction(card3, card1).handle(setState);
expect(testState6.currentCards.value.length).toBe(3, 'Failed to add third card');
expect(testState6.currentCards.value[0]).toBe(card3, 'Failed to insert card at start of the list');
});
@ -472,8 +462,7 @@ describe('AppService Reducer', () => {
type: CardType.Strongs,
};
const action1 = AppActionFactory.newAddCard(oldCard, null);
const preState1 = reducer(preState, action1);
const preState1 = addCardAction(oldCard, null).handle(preState);
const newCard: CardItem = {
qry: 'H88',
@ -481,8 +470,7 @@ describe('AppService Reducer', () => {
type: CardType.Strongs,
};
const action2 = AppActionFactory.newUpdateCard(newCard, oldCard);
const testState = reducer(preState1, action2);
const testState = updateCardAction(newCard, oldCard).handle(preState1);
expect(testState.currentCards.value[0].qry).toBe('H88', 'Should update the card');
expect(testState.cardCache[getCardCacheKey(newCard)].qry).toBe('H88', 'Should exist in card cache');
@ -498,13 +486,14 @@ describe('AppService Reducer', () => {
type: CardType.Strongs,
};
const action1 = AppActionFactory.newAddCard(card, null);
const preState1 = reducer(preState, action1);
const preState1 = addCardAction(card, null).handle(preState);
const action2 = AppActionFactory.newRemoveCard(card);
const testState = reducer(preState1, action2);
const testState = removeCardAction(card).handle(preState1);
expect(preState1.currentCards.value.length).toBe(1, 'Should have added the card in preparation for removing the card');
expect(preState1.currentCards.value.length).toBe(
1,
'Should have added the card in preparation for removing the card'
);
expect(testState.currentCards.value.length).toBe(0, 'Should have removed the card');
expect(testState.cardCache[getCardCacheKey(card)]).toBeUndefined(
'Should be undefined, having been removed from card cache'
@ -518,33 +507,30 @@ describe('AppService Reducer', () => {
type: CardType.Strongs,
};
const action1 = AppActionFactory.newAddCard(card1, null);
const preState1 = reducer(preState, action1);
const preState1 = addCardAction(card1, null).handle(preState);
const card2: CardItem = {
qry: 'H88',
data: null,
type: CardType.Strongs,
};
const action2 = AppActionFactory.newAddCard(card2, null);
const preState2 = reducer(preState1, action2);
const preState2 = addCardAction(card2, null).handle(preState1);
expect(preState2.currentCards.value.length).toBe(2, 'Should have two cards');
expect(preState2.currentCards.value[0].qry).toBe('G123');
expect(preState2.currentCards.value[1].qry).toBe('H88');
const action3 = AppActionFactory.newMoveCard(card2, MoveDirection.Up);
const testState1 = reducer(preState2, action3);
const testState1 = moveCardAction(card2, MoveDirection.Up).handle(preState2);
expect(testState1.currentCards.value[0].qry).toBe('H88');
expect(testState1.currentCards.value[1].qry).toBe('G123');
const testState4 = reducer(preState2, action3);
const testState4 = moveCardAction(card2, MoveDirection.Up).handle(preState2);
expect(testState4.currentCards.value[0].qry).toBe('H88');
expect(testState4.currentCards.value[1].qry).toBe('G123');
const action4 = AppActionFactory.newMoveCard(card1, MoveDirection.Down);
const testState2 = reducer(preState2, action4);
const testState2 = moveCardAction(card1, MoveDirection.Down).handle(preState2);
expect(testState2.currentCards.value[0].qry).toBe('H88');
expect(testState2.currentCards.value[1].qry).toBe('G123');
const testState3 = reducer(preState2, action4);
const testState3 = moveCardAction(card1, MoveDirection.Down).handle(preState2);
expect(testState3.currentCards.value[0].qry).toBe('H88');
expect(testState3.currentCards.value[1].qry).toBe('G123');
});
@ -559,8 +545,7 @@ describe('AppService Reducer', () => {
providerId: 'asdfasf',
};
const action1 = AppActionFactory.newUser(user);
const testState = reducer(preState, action1);
const testState = newUserAction(user).handle(preState);
expect(testState.user).toBe(user, 'Should set the user');
});
@ -581,18 +566,14 @@ describe('AppService Reducer', () => {
xref: 'jn 3:16',
};
const action1 = AppActionFactory.newSaveNote(note1);
const preState1 = reducer(preState, action1);
const action2 = AppActionFactory.newSaveNote(note2);
const preState2 = reducer(preState1, action2);
const preState1 = saveNoteAction(note1).handle(preState);
const preState2 = saveNoteAction(note2).handle(preState1);
expect(preState2.notes.value.length).toBe(2, 'Should have two notes');
const action3 = AppActionFactory.newGetNote('123456789', null);
const preState3 = reducer(preState2, action3);
const preState3 = getNotesAction('123456789', null).handle(preState2);
expect(preState3.currentCards.value.length).toBe(1, 'Should have added the note card');
const action4 = AppActionFactory.newGetNote('1234567890', null);
const preState4 = reducer(preState3, action4);
const preState4 = getNotesAction('1234567890', null).handle(preState3);
expect(preState4.currentCards.value.length).toBe(2, 'Should have added the note card');
});
@ -610,19 +591,20 @@ describe('AppService Reducer', () => {
xref: 'jn 3:16',
};
const action1 = AppActionFactory.newUpdateNotes(new Storable([note1, note2]));
const preState1 = reducer(preState, action1);
const preState1 = updateNotesAction(new Storable([note1, note2])).handle(preState);
expect(preState1.notes.value.length).toBe(2, 'Should have added the notes');
const action2 = AppActionFactory.newFindNotes('note', null);
const preState2 = reducer(preState1, action2);
const preState2 = findNotesAction('note', null).handle(preState1);
expect(preState2.currentCards.value.length).toBe(2, 'Should have found two notes card');
const action3 = AppActionFactory.newDeleteNote(note1);
const preState3 = reducer(preState2, action3);
const preState3 = deleteNoteAction(note1).handle(preState2);
expect(preState3.currentCards.value.length).toBe(1, 'Should have deleted the note card');
expect(preState3.notes.value.length).toBe(1, 'Should have added the notes');
});
//#endregion
});
function newUserAction(preState: AppState, action1: any) {
throw new Error('Function not implemented.');
}

View File

@ -1,489 +0,0 @@
import { UUID } from 'angular2-uuid';
import { AppState, Settings } from '../models/app-state';
import { IStorable, Storable } from '../common/storable';
import { NoteItem } from '../models/note-state';
import { mergeCardList } from '../common/card-operations';
import { updateInCardCache, removeFromCardCache, getFromCardCache } from '../common/card-cache-operations';
import { AppAction, AppActionFactory } from './app-state-actions';
import { initialState } from './app-state-initial-state';
import { SavedPage } from '../models/page-state';
import { CardType, CardItem, DataReference } from '../models/card-state';
import { moveItem, moveItemUpOrDown } from '../common/array-operations';
export function getNewestStorable<T>(candidate: IStorable<T>, incumbant: IStorable<T>): IStorable<T> {
// if the candidate is null, then return the state.
if (!candidate) {
return incumbant;
}
// only update if the settings are newer.
if (!incumbant || new Date(candidate.createdOn) > new Date(incumbant.createdOn)) {
return candidate;
}
// candidate didn't win. return state untouched.
return incumbant;
}
export function reducer(state: AppState, action: AppAction): AppState {
// somtimes the state is null. lets not complain if that happens.
if (state === undefined) {
state = initialState;
}
switch (action.type) {
case 'UPDATE_ERROR': {
return {
...state,
error: action.error,
};
}
//#region Settings
case 'UPDATE_CARD_MERGE_STRATEGY': {
const settings = new Storable<Settings>({
...state.settings.value,
pageSettings: {
...state.settings.value.pageSettings,
mergeStrategy: action.cardMergeStrategy,
},
});
return reducer(state, {
type: 'UPDATE_SETTINGS',
settings,
});
}
case 'UPDATE_SETTINGS': {
const item = getNewestStorable(action.settings, state.settings);
return {
...state,
settings: item,
};
}
case 'UPDATE_CARD_FONT_SIZE': {
const settings = new Storable<Settings>({
...state.settings.value,
displaySettings: {
...state.settings.value.displaySettings,
cardFontSize: action.cardFontSize,
},
});
return reducer(state, {
type: 'UPDATE_SETTINGS',
settings,
});
}
case 'UPDATE_CARD_FONT_FAMILY': {
const settings = new Storable<Settings>({
...state.settings.value,
displaySettings: {
...state.settings.value.displaySettings,
cardFontFamily: action.cardFontFamily,
},
});
return reducer(state, {
type: 'UPDATE_SETTINGS',
settings,
});
}
//#endregion
case 'UPDATE_AUTOCOMPLETE': {
return {
...state,
autocomplete: [...action.words],
};
}
//#region Saved Pages
case 'UPDATE_SAVED_PAGES': {
const savedPages = getNewestStorable(action.savedPages, state.savedPages);
// only true if a currentSavedPage was set, indicating that the user
// is currently looking at a saved page.
const hasCurrentSavedPage =
state.currentSavedPage !== null &&
state.currentSavedPage !== undefined &&
action.savedPages.value.some((o) => o.id === state.currentSavedPage.id);
const currentSavedPage = hasCurrentSavedPage
? action.savedPages.value.find((o) => o.id === state.currentSavedPage.id)
: null;
return {
...state,
// if the currentSavedPage was loaded, replace it with the info from the
// new savedPages array, as it might have changed.
currentCards: hasCurrentSavedPage ? new Storable(currentSavedPage.queries) : state.currentCards,
currentSavedPage: hasCurrentSavedPage ? currentSavedPage : state.currentSavedPage,
savedPagesLoaded: true,
savedPages, // update the savedPages
};
}
case 'UPDATE_SAVED_PAGE': {
const newSavedPages = new Storable<SavedPage[]>(
state.savedPages.value.map((o) => {
if (o.id === action.savedPage.id) {
return action.savedPage;
}
return o;
})
);
const savedPages = getNewestStorable(newSavedPages, state.savedPages);
return reducer(state, AppActionFactory.newUpdateSavedPages(savedPages));
}
case 'REMOVE_SAVED_PAGE': {
const savedPages = new Storable<SavedPage[]>(state.savedPages.value.filter((o) => o.id !== action.savedPage.id));
const item = getNewestStorable(savedPages, state.savedPages);
return {
...state,
savedPagesLoaded: true,
savedPages: item,
};
}
case 'UPDATE_CURRENT_PAGE': {
const current = {
id: state.currentSavedPage.id,
title: state.currentSavedPage.title,
queries: [...mergeCardList(state.currentCards.value, state.settings.value.pageSettings.mergeStrategy)],
};
const savedPages = new Storable<SavedPage[]>([
...state.savedPages.value.filter((o) => o.id !== state.currentSavedPage.id),
current,
]);
const item = getNewestStorable(savedPages, state.savedPages);
return {
...state,
currentSavedPage: current,
savedPagesLoaded: true,
savedPages: item,
};
}
case 'SAVE_PAGE': {
const savedPages = new Storable([
...(state.savedPages ? state.savedPages.value : []),
{
// create a new saved page object
title: action.title,
id: UUID.UUID().toString(),
queries: [...mergeCardList(state.currentCards.value, state.settings.value.pageSettings.mergeStrategy)],
},
]);
return reducer(state, {
type: 'UPDATE_SAVED_PAGES',
savedPages,
});
}
case 'MOVE_SAVED_PAGE_CARD': {
const queries = moveItem(action.savedPage.queries, action.fromIndex, action.toIndex);
const savedPage = {
...action.savedPage,
queries, // update the queries.
};
return reducer(state, AppActionFactory.newUpdateSavedPage(savedPage));
}
case 'ADD_CARD_TO_SAVED_PAGE': {
const savedPages = new Storable([
...(state.savedPages ? state.savedPages.value : []).map((o) => {
if (o.id.toString() === action.pageId) {
let references = [] as DataReference[];
if (state.settings.value.displaySettings.appendCardToBottom) {
references = [...o.queries, action.card];
} else {
references = [action.card, ...o.queries];
}
return {
...o,
queries: mergeCardList(references, state.settings.value.pageSettings.mergeStrategy),
};
}
return o;
}),
]);
return reducer(state, {
type: 'UPDATE_SAVED_PAGES',
savedPages,
});
}
//#endregion
//#region Cards
case 'ADD_CARD': {
let cards = [];
if (action.nextToItem && state.settings.value.displaySettings.insertCardNextToItem) {
const idx = state.currentCards.value.indexOf(action.nextToItem);
if (state.settings.value.displaySettings.appendCardToBottom) {
const before = state.currentCards.value.slice(0, idx + 1);
const after = state.currentCards.value.slice(idx + 1);
cards = [...before, action.card, ...after];
} else {
const before = state.currentCards.value.slice(0, idx);
const after = state.currentCards.value.slice(idx);
cards = [...before, action.card, ...after];
}
} else {
if (state.settings.value.displaySettings.appendCardToBottom) {
cards = [...state.currentCards.value, action.card];
} else {
cards = [action.card, ...state.currentCards.value];
}
}
return {
...state,
currentCards: new Storable(cards),
cardCache: updateInCardCache(action.card, state.cardCache),
};
}
case 'UPDATE_CARD': {
return {
...state,
currentCards: new Storable(
state.currentCards.value.map((c) => {
if (c === action.oldCard) {
return action.newCard;
}
return c;
})
),
cardCache: updateInCardCache(action.newCard, removeFromCardCache(action.oldCard, state.cardCache)),
};
}
case 'REMOVE_CARD': {
// potentially remove card from a saved page.
const currentSavedPage =
state.currentSavedPage !== null
? {
...state.currentSavedPage,
queries: state.currentSavedPage.queries.filter((q) => q !== action.card),
}
: null;
const savedPages =
state.currentSavedPage !== null
? new Storable(
(state.savedPages ? state.savedPages.value : []).map((o) => {
if (o === state.currentSavedPage) {
return currentSavedPage;
}
return o;
})
)
: state.savedPages;
return {
...state,
currentSavedPage,
savedPages,
currentCards: new Storable([...state.currentCards.value.filter((c) => c !== action.card)]),
cardCache: removeFromCardCache(action.card, state.cardCache),
};
}
case 'CLEAR_CARDS': {
// potentially remove card from a saved page.
return {
...state,
currentCards: new Storable([]),
};
}
case 'MOVE_CARD': {
const cards = moveItemUpOrDown(state.currentCards.value, action.card, action.direction);
return {
...state,
currentCards: new Storable(cards),
};
}
case 'UPDATE_CARDS': {
let cardCache = { ...state.cardCache };
for (const card of action.cards.value) {
cardCache = updateInCardCache(card, cardCache);
}
return {
...state,
currentCards: action.cards,
cardCache,
};
}
//#endregion
case 'SET_USER': {
return {
...state,
user: action.user,
};
}
//#region Notes
case 'FIND_NOTES': {
const notes = state.notes.value
.filter((o) => o.title.search(action.qry) > -1)
.map((o) => {
return {
qry: o.id,
type: CardType.Note,
data: o,
} as CardItem;
});
let cards = [] as DataReference[];
if (action.nextToItem && state.settings.value.displaySettings.insertCardNextToItem) {
const idx = state.currentCards.value.indexOf(action.nextToItem);
if (state.settings.value.displaySettings.appendCardToBottom) {
const before = state.currentCards.value.slice(0, idx + 1);
const after = state.currentCards.value.slice(idx + 1);
cards = [...before, ...notes, ...after];
} else {
const before = state.currentCards.value.slice(0, idx);
const after = state.currentCards.value.slice(idx);
cards = [...before, ...notes, ...after];
}
} else {
if (state.settings.value.displaySettings.appendCardToBottom) {
cards = [...state.currentCards.value, ...notes];
} else {
cards = [...notes, ...state.currentCards.value];
}
}
let cache = { ...state.cardCache };
for (const n of notes) {
cache = updateInCardCache(n, cache);
}
return {
...state,
currentCards: new Storable(cards),
cardCache: cache,
};
}
case 'GET_NOTE': {
const note = state.notes.value.find((o) => o.id === action.noteId);
const card: CardItem = {
qry: note.id,
type: CardType.Note,
data: note,
};
return reducer(state, {
type: 'ADD_CARD',
card,
nextToItem: action.nextToItem,
});
}
case 'UPDATE_NOTES': {
return {
...state,
notes: action.notes ? action.notes : new Storable([]),
};
}
case 'SAVE_NOTE': {
// you may be creating a new note or updating an existing.
// if its an update, you need to update the note in the following:
// * card list could have it.
// * notes list could have it.
// * it could be in any of the saved pages lists...
// so iterate through all of them and if you find the note
// in any of them, swap it out
const notes = new Storable<NoteItem[]>([
...state.notes.value.filter((o) => o.id !== action.note.id),
action.note,
]);
const newState = {
...state,
currentCards: new Storable([...state.currentCards.value]), // you want to trigger an update to the cards if a card update is different.
notes,
};
return newState;
}
case 'DELETE_NOTE': {
// the note may be in any of the following:
// * card list could have it.
// * notes list could have it.
// * it could be in any of the saved pages lists...
// so iterate through all of them and if you find the note
// in any of them, remove it
const card = state.currentCards.value.find((o) => o.qry === action.note.id);
const cards = card
? [
...state.currentCards.value.filter((o) => {
return o.type !== CardType.Note || o.qry !== action.note.id;
}),
]
: state.currentCards.value; // if card is undefined, then it wasn't in the current card list.
const notes = new Storable<NoteItem[]>([...state.notes.value.filter((o) => o.id !== action.note.id)]);
const savedPages = new Storable<SavedPage[]>([
...(state.savedPages ? state.savedPages.value : []).map((sp) => {
return {
...sp,
queries: sp.queries.filter((o) => {
return o.type !== CardType.Note || o.qry !== action.note.id;
}),
};
}),
]);
return {
...state,
currentCards: new Storable(cards),
notes,
savedPages,
cardCache: card
? removeFromCardCache(getFromCardCache(card, state.cardCache), state.cardCache)
: state.cardCache,
};
}
//#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,3 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"compileOnSave": false,
"compilerOptions": {