mirror of
https://gitlab.com/walljm/dynamicbible.git
synced 2025-07-23 07:19:50 -04:00
implement a card cache, this might not be working yet
This commit is contained in:
parent
0aeb891b32
commit
470aac1342
@ -1,7 +1,11 @@
|
||||
import { BibleReference, Overlap } from './bible-reference';
|
||||
import { CardItem, CardType } from '../models/card-state';
|
||||
import { CardType, DataReference } from '../models/card-state';
|
||||
|
||||
export function maybeMergeCards(leftCard: CardItem, rightCard: CardItem, strategy: Overlap): CardItem | null {
|
||||
export function maybeMergeCards(
|
||||
leftCard: DataReference,
|
||||
rightCard: DataReference,
|
||||
strategy: Overlap
|
||||
): DataReference | null {
|
||||
if (leftCard.type === rightCard.type) {
|
||||
switch (strategy) {
|
||||
case Overlap.Equal:
|
||||
@ -28,7 +32,7 @@ export function maybeMergeCards(leftCard: CardItem, rightCard: CardItem, strateg
|
||||
return null;
|
||||
}
|
||||
|
||||
export function mergeCardList(cardList: readonly CardItem[], strategy: Overlap): CardItem[] {
|
||||
export function mergeCardList(cardList: readonly DataReference[], strategy: Overlap) {
|
||||
if (strategy === Overlap.None || cardList.length === 0) {
|
||||
return [...cardList];
|
||||
}
|
||||
|
@ -4,11 +4,14 @@ import { MatDialog } from '@angular/material/dialog';
|
||||
import { AppService } from '../../services/app.service';
|
||||
import { Observable } from 'rxjs';
|
||||
import { SavedPage } from 'src/app/models/page-state';
|
||||
import { CardItem, CardType } from 'src/app/models/card-state';
|
||||
import { CardItem, CardType, DataReference } from 'src/app/models/card-state';
|
||||
import { NoteItem } from 'src/app/models/note-state';
|
||||
import { OkCancelModalComponent, OkCancelResult } from '../ok-cancel-modal/ok-cancel-modal.component';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { PageEditModalComponent } from '../page-edit-modal/page-edit-modal.component';
|
||||
import { HashTable } from 'src/app/common/hashtable';
|
||||
import { SubscriberBase } from 'src/app/common/subscriber-base';
|
||||
import { getCardFromCache } from 'src/app/services/app-state-reducer';
|
||||
|
||||
@Component({
|
||||
selector: 'app-saved-page-card',
|
||||
@ -16,19 +19,29 @@ import { PageEditModalComponent } from '../page-edit-modal/page-edit-modal.compo
|
||||
styleUrls: ['./saved-page-card.component.scss'],
|
||||
preserveWhitespaces: true,
|
||||
})
|
||||
export class SavedPageCardComponent implements OnInit {
|
||||
export class SavedPageCardComponent extends SubscriberBase implements OnInit {
|
||||
icon$: Observable<string>;
|
||||
cache: HashTable<CardItem>;
|
||||
|
||||
@Input()
|
||||
savedPage: SavedPage;
|
||||
|
||||
constructor(protected appService: AppService, public dialog: MatDialog, private snackBar: MatSnackBar) {
|
||||
super();
|
||||
|
||||
this.icon$ = appService.select((state) => state.settings.value.cardIcons.savedPage);
|
||||
this.addSubscription(
|
||||
this.appService
|
||||
.select((state) => state.cardCache)
|
||||
.subscribe((cache) => {
|
||||
this.cache = cache;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
format(item: CardItem) {
|
||||
format(item: DataReference) {
|
||||
if (item.type === CardType.Note) {
|
||||
return `Note: ${(item.data as NoteItem).title}`;
|
||||
return `Note: ${(getCardFromCache(item, this.cache).data as NoteItem).title}`;
|
||||
} else if (item.type === CardType.Passage) {
|
||||
return `Passage: ${item.qry}`;
|
||||
} else if (item.type === CardType.Strongs) {
|
||||
|
@ -1,12 +1,15 @@
|
||||
import { IStorable } from '../common/storable';
|
||||
import { NoteItem } from './note-state';
|
||||
import { Overlap } from '../common/bible-reference';
|
||||
import { CardItem, CardIcons } from './card-state';
|
||||
import { CardItem, CardIcons, DataReference } from './card-state';
|
||||
import { SavedPage } from './page-state';
|
||||
import { HashTable } from '../common/hashtable';
|
||||
|
||||
export interface AppState {
|
||||
readonly currentSavedPage: SavedPage;
|
||||
readonly currentCards: readonly CardItem[];
|
||||
readonly currentCards: readonly DataReference[];
|
||||
|
||||
readonly cardCache: HashTable<CardItem>;
|
||||
|
||||
readonly savedPages: IStorable<readonly SavedPage[]>;
|
||||
readonly notes: IStorable<readonly NoteItem[]>;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { CardItem } from './card-state';
|
||||
import { DataReference } from './card-state';
|
||||
|
||||
export interface SavedPage {
|
||||
readonly queries: readonly CardItem[];
|
||||
readonly queries: readonly DataReference[];
|
||||
readonly title: string;
|
||||
readonly id: string;
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import { SubscriberBase } from '../../common/subscriber-base';
|
||||
import { BibleReference } from '../../common/bible-reference';
|
||||
import { VersePickerModalComponent } from '../../components/verse-picker-modal/verse-picker-modal.component';
|
||||
import { CardItem, CardType } from 'src/app/models/card-state';
|
||||
import { getCardFromCache } from 'src/app/services/app-state-reducer';
|
||||
|
||||
@Component({
|
||||
selector: 'app-search-page',
|
||||
@ -17,7 +18,7 @@ import { CardItem, CardType } from 'src/app/models/card-state';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class SearchPage extends SubscriberBase implements OnInit {
|
||||
cards$ = this.appService.select((state) => state.currentCards);
|
||||
cards$ = this.appService.select((state) => state.currentCards.map((o) => getCardFromCache(o, state.cardCache)));
|
||||
suggestions$ = this.appService.select((state) => state.autocomplete);
|
||||
|
||||
savedPagedLoaded = false;
|
||||
|
@ -96,6 +96,13 @@ export class AppActionFactory {
|
||||
} as AppAction;
|
||||
}
|
||||
|
||||
static newUpdateCards(cards: CardItem[]): AppAction {
|
||||
return {
|
||||
type: 'UPDATE_CARDS',
|
||||
cards,
|
||||
} as AppAction;
|
||||
}
|
||||
|
||||
static newUpdateError(error: Error): AppAction {
|
||||
return {
|
||||
type: 'UPDATE_ERROR',
|
||||
@ -236,6 +243,10 @@ export type AppAction =
|
||||
card: CardItem;
|
||||
direction: MoveDirection;
|
||||
}
|
||||
| {
|
||||
type: 'UPDATE_CARDS';
|
||||
cards: CardItem[];
|
||||
}
|
||||
| {
|
||||
type: 'UPDATE_ERROR';
|
||||
error: Error;
|
||||
|
@ -4,6 +4,7 @@ import { Overlap } from '../common/bible-reference';
|
||||
export const initialState: AppState = {
|
||||
user: null,
|
||||
currentCards: [],
|
||||
cardCache: {},
|
||||
autocomplete: [],
|
||||
currentSavedPage: null,
|
||||
savedPages: null,
|
||||
|
@ -1,11 +1,13 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { reducer, getNewestStorable } from './app-state-reducer';
|
||||
import { reducer, getNewestStorable, updateCache, getCardItemKey, removeFromCache } from './app-state-reducer';
|
||||
import { AppActionFactory } from './app-state-actions';
|
||||
import { Overlap } from '../common/bible-reference';
|
||||
import { Storable } from '../common/storable';
|
||||
import { CardType, CardItem } from '../models/card-state';
|
||||
import { SavedPage } from '../models/page-state';
|
||||
import { AppState } from '../models/app-state';
|
||||
import { HashTable } from '../common/hashtable';
|
||||
import { NoteItem } from '../models/note-state';
|
||||
|
||||
describe('getNewestStorable', () => {
|
||||
it('maybeMutateStorable', () => {
|
||||
@ -26,10 +28,82 @@ describe('getNewestStorable', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Card Cache', () => {
|
||||
it('updateCache', () => {
|
||||
const card1: CardItem = {
|
||||
qry: 'jason',
|
||||
type: CardType.Passage,
|
||||
data: null,
|
||||
};
|
||||
const card2: CardItem = {
|
||||
qry: 'jason',
|
||||
type: CardType.Passage,
|
||||
data: {
|
||||
id: 'adsf',
|
||||
xref: '',
|
||||
title: 'adsf',
|
||||
content: '',
|
||||
} as NoteItem,
|
||||
};
|
||||
const card3: CardItem = {
|
||||
qry: 'jason3',
|
||||
type: CardType.Passage,
|
||||
data: null,
|
||||
};
|
||||
|
||||
const cache: HashTable<CardItem> = {};
|
||||
let newCache = updateCache(card1, cache);
|
||||
|
||||
expect(newCache[getCardItemKey(card1)].qry).toBe('jason', 'Should have added the card');
|
||||
expect(newCache[getCardItemKey(card1)].data).toBe(null, 'Should have null data');
|
||||
|
||||
newCache = updateCache(card2, newCache);
|
||||
expect(newCache[getCardItemKey(card2)].qry).toBe('jason', 'Should have added the card');
|
||||
expect((newCache[getCardItemKey(card1)].data as NoteItem).title).toBe('adsf', 'Should have added the card');
|
||||
expect(Object.keys(newCache).length).toBe(1, 'Should still have only 1 item.');
|
||||
|
||||
newCache = updateCache(card3, newCache);
|
||||
expect(newCache[getCardItemKey(card3)].qry).toBe('jason3', 'Should have added the card');
|
||||
expect(Object.keys(newCache).length).toBe(2, 'Should have 2 items.');
|
||||
});
|
||||
|
||||
it('removeFromCache', () => {
|
||||
const card1: CardItem = {
|
||||
qry: 'jason',
|
||||
type: CardType.Passage,
|
||||
data: null,
|
||||
};
|
||||
const card2: CardItem = {
|
||||
qry: 'jason',
|
||||
type: CardType.Passage,
|
||||
data: {
|
||||
id: 'adsf',
|
||||
xref: '',
|
||||
title: 'adsf',
|
||||
content: '',
|
||||
} as NoteItem,
|
||||
};
|
||||
const card3: CardItem = {
|
||||
qry: 'jason3',
|
||||
type: CardType.Passage,
|
||||
data: null,
|
||||
};
|
||||
|
||||
const cache: HashTable<CardItem> = {};
|
||||
let newCache = updateCache(card1, cache);
|
||||
newCache = updateCache(card3, newCache);
|
||||
expect(Object.keys(newCache).length).toBe(2, 'Should have 2 items.');
|
||||
|
||||
newCache = removeFromCache(card1, newCache);
|
||||
expect(Object.keys(newCache).length).toBe(1, 'Should remove 1 item');
|
||||
});
|
||||
});
|
||||
|
||||
describe('AppService Reducer', () => {
|
||||
const preState = {
|
||||
user: null,
|
||||
currentCards: [],
|
||||
cardCache: {},
|
||||
autocomplete: [],
|
||||
currentSavedPage: {
|
||||
queries: [],
|
||||
@ -109,6 +183,8 @@ describe('AppService Reducer', () => {
|
||||
TestBed.configureTestingModule({});
|
||||
});
|
||||
|
||||
//#region Settings
|
||||
|
||||
it('UPDATE_CARD_MERGE_STRATEGY', () => {
|
||||
for (const strategy of [Overlap.None, Overlap.Equal, Overlap.Subset, Overlap.Intersect]) {
|
||||
const action = AppActionFactory.newUpdateCardMergeStrategy(strategy);
|
||||
@ -169,6 +245,8 @@ describe('AppService Reducer', () => {
|
||||
);
|
||||
});
|
||||
|
||||
//#endregion
|
||||
|
||||
it('UPDATE_AUTOCOMPLETE', () => {
|
||||
const words = ['word1', 'word2', 'word3'];
|
||||
|
||||
@ -177,6 +255,8 @@ describe('AppService Reducer', () => {
|
||||
expect(testState.autocomplete).toEqual(words, 'Failed to update the autocomplete array');
|
||||
});
|
||||
|
||||
//#region Saved Pages
|
||||
|
||||
it('UPDATE_SAVED_PAGES', () => {
|
||||
const savedPages = new Storable<readonly SavedPage[]>([
|
||||
{
|
||||
@ -253,6 +333,7 @@ describe('AppService Reducer', () => {
|
||||
|
||||
// 'SAVE_PAGE';
|
||||
// 'GET_SAVED_PAGE';
|
||||
|
||||
it('MOVE_SAVED_PAGE_CARD', () => {
|
||||
const page = preState.savedPages.value[1];
|
||||
|
||||
@ -263,6 +344,10 @@ describe('AppService Reducer', () => {
|
||||
|
||||
// 'ADD_CARD_TO_SAVED_PAGE';
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Cards
|
||||
|
||||
it('ADD_CARD', () => {
|
||||
const card1: CardItem = {
|
||||
qry: 'H123',
|
||||
@ -415,10 +500,18 @@ describe('AppService Reducer', () => {
|
||||
// 'UPDATE_CARD';
|
||||
// 'REMOVE_CARD';
|
||||
// 'MOVE_CARD';
|
||||
|
||||
//#endregion
|
||||
|
||||
// 'SET_USER';
|
||||
|
||||
//#region Notes
|
||||
|
||||
// 'FIND_NOTES';
|
||||
// 'GET_NOTE';
|
||||
// 'UPDATE_NOTES';
|
||||
// 'SAVE_NOTE';
|
||||
// 'DELETE_NOTE';
|
||||
|
||||
//#endregion
|
||||
});
|
||||
|
@ -9,8 +9,9 @@ import { mergeCardList } from '../common/card-operations';
|
||||
import { AppAction, AppActionFactory } from './app-state-actions';
|
||||
import { initialState } from './app-state-initial-state';
|
||||
import { SavedPage } from '../models/page-state';
|
||||
import { CardType, CardItem } from '../models/card-state';
|
||||
import { CardType, CardItem, DataReference } from '../models/card-state';
|
||||
import { moveItem, moveItemUpOrDown } from '../common/array-operations';
|
||||
import { HashTable } from '../common/hashtable';
|
||||
|
||||
export function getNewestStorable<T>(candidate: IStorable<T>, incumbant: IStorable<T>): IStorable<T> {
|
||||
// if the candidate is null, then return the state.
|
||||
@ -27,6 +28,27 @@ export function getNewestStorable<T>(candidate: IStorable<T>, incumbant: IStorab
|
||||
return incumbant;
|
||||
}
|
||||
|
||||
export function updateCache(card: CardItem, cardCache: HashTable<CardItem>): HashTable<CardItem> {
|
||||
const cache = { ...cardCache };
|
||||
cache[getCardItemKey(card)] = card;
|
||||
return cache;
|
||||
}
|
||||
|
||||
export function removeFromCache(card: CardItem, cardCache: HashTable<CardItem>): HashTable<CardItem> {
|
||||
const cache = { ...cardCache };
|
||||
delete cache[getCardItemKey(card)];
|
||||
return cache;
|
||||
}
|
||||
|
||||
export function getCardItemKey(card: DataReference) {
|
||||
return `${card.qry}:${card.type}`;
|
||||
}
|
||||
|
||||
export function getCardFromCache(ref: DataReference, cardCache: HashTable<CardItem>) {
|
||||
const key = getCardItemKey(ref);
|
||||
return cardCache[key];
|
||||
}
|
||||
|
||||
export function reducer(state: AppState, action: AppAction): AppState {
|
||||
// somtimes the state is null. lets not complain if that happens.
|
||||
if (state === undefined) {
|
||||
@ -41,7 +63,7 @@ export function reducer(state: AppState, action: AppAction): AppState {
|
||||
};
|
||||
}
|
||||
|
||||
//#region Saved Page Settings
|
||||
//#region Settings
|
||||
|
||||
case 'UPDATE_CARD_MERGE_STRATEGY': {
|
||||
const settings = new Storable<Settings>({
|
||||
@ -57,10 +79,6 @@ export function reducer(state: AppState, action: AppAction): AppState {
|
||||
});
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Display Settings
|
||||
|
||||
case 'UPDATE_SETTINGS': {
|
||||
const item = getNewestStorable(action.settings, state.settings);
|
||||
|
||||
@ -218,15 +236,15 @@ export function reducer(state: AppState, action: AppAction): AppState {
|
||||
const savedPages = new Storable([
|
||||
...(state.savedPages ? state.savedPages.value : []).map((o) => {
|
||||
if (o.id.toString() === action.pageId) {
|
||||
let cards = [] as CardItem[];
|
||||
let references = [] as DataReference[];
|
||||
if (state.settings.value.displaySettings.appendCardToBottom) {
|
||||
cards = [...o.queries, action.card];
|
||||
references = [...o.queries, action.card];
|
||||
} else {
|
||||
cards = [action.card, ...o.queries];
|
||||
references = [action.card, ...o.queries];
|
||||
}
|
||||
return {
|
||||
...o,
|
||||
queries: mergeCardList(cards, state.settings.value.pageSettings.mergeStrategy),
|
||||
queries: mergeCardList(references, state.settings.value.pageSettings.mergeStrategy),
|
||||
};
|
||||
}
|
||||
return o;
|
||||
@ -265,9 +283,11 @@ export function reducer(state: AppState, action: AppAction): AppState {
|
||||
cards = [action.card, ...state.currentCards];
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
currentCards: cards,
|
||||
cardCache: updateCache(action.card, state.cardCache),
|
||||
};
|
||||
}
|
||||
case 'UPDATE_CARD': {
|
||||
@ -279,6 +299,7 @@ export function reducer(state: AppState, action: AppAction): AppState {
|
||||
}
|
||||
return c;
|
||||
}),
|
||||
cardCache: updateCache(action.newCard, removeFromCache(action.oldCard, state.cardCache)),
|
||||
};
|
||||
}
|
||||
case 'REMOVE_CARD': {
|
||||
@ -308,6 +329,7 @@ export function reducer(state: AppState, action: AppAction): AppState {
|
||||
currentSavedPage,
|
||||
savedPages,
|
||||
currentCards: [...state.currentCards.filter((c) => c !== action.card)],
|
||||
cardCache: removeFromCache(action.card, state.cardCache),
|
||||
};
|
||||
}
|
||||
case 'MOVE_CARD': {
|
||||
@ -319,6 +341,17 @@ export function reducer(state: AppState, action: AppAction): AppState {
|
||||
};
|
||||
}
|
||||
|
||||
case 'UPDATE_CARDS': {
|
||||
let cardCache = { ...state.cardCache };
|
||||
for (const card of action.cards) {
|
||||
cardCache = updateCache(card, cardCache);
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
currentCards: action.cards,
|
||||
cardCache,
|
||||
};
|
||||
}
|
||||
//#endregion
|
||||
|
||||
case 'SET_USER': {
|
||||
@ -341,7 +374,7 @@ export function reducer(state: AppState, action: AppAction): AppState {
|
||||
} as CardItem;
|
||||
});
|
||||
|
||||
let cards = [] as CardItem[];
|
||||
let cards = [] as DataReference[];
|
||||
|
||||
if (action.nextToItem && state.settings.value.displaySettings.insertCardNextToItem) {
|
||||
const idx = state.currentCards.indexOf(action.nextToItem);
|
||||
@ -398,7 +431,7 @@ export function reducer(state: AppState, action: AppAction): AppState {
|
||||
|
||||
const cards = [
|
||||
...state.currentCards.map((o) => {
|
||||
const n = o.data as NoteItem;
|
||||
const n = getCardFromCache(o, state.cardCache).data as NoteItem;
|
||||
if (n && n.id === action.note.id) {
|
||||
return {
|
||||
...o,
|
||||
@ -419,7 +452,7 @@ export function reducer(state: AppState, action: AppAction): AppState {
|
||||
return {
|
||||
...sp,
|
||||
queries: sp.queries.map((o) => {
|
||||
const n = o.data as NoteItem;
|
||||
const n = getCardFromCache(o, state.cardCache).data as NoteItem;
|
||||
if (n && n.id === action.note.id) {
|
||||
return {
|
||||
...o,
|
||||
@ -449,7 +482,7 @@ export function reducer(state: AppState, action: AppAction): AppState {
|
||||
|
||||
const cards = [
|
||||
...state.currentCards.filter((o) => {
|
||||
const n = o.data as NoteItem;
|
||||
const n = getCardFromCache(o, state.cardCache).data as NoteItem;
|
||||
return !n || n.id !== action.note.id;
|
||||
}),
|
||||
];
|
||||
@ -461,7 +494,7 @@ export function reducer(state: AppState, action: AppAction): AppState {
|
||||
return {
|
||||
...sp,
|
||||
queries: sp.queries.filter((o) => {
|
||||
const n = o.data as NoteItem;
|
||||
const n = getCardFromCache(o, state.cardCache).data as NoteItem;
|
||||
return !n || n.id !== action.note.id;
|
||||
}),
|
||||
};
|
||||
|
@ -84,12 +84,55 @@ export class AppService extends createStateService(reducer, initialState) {
|
||||
|
||||
//#endregion
|
||||
|
||||
async getCardByQuery(qry: string): Promise<CardItem> {
|
||||
if (qry.startsWith('note:')) {
|
||||
const id = qry.replace('note:', '');
|
||||
const data = this.getState().notes.value.find((o) => o.id === id);
|
||||
return {
|
||||
qry,
|
||||
type: CardType.Note,
|
||||
data,
|
||||
} as CardItem;
|
||||
} else if (qry.search(/[0-9]/i) === -1) {
|
||||
// // its a search term.
|
||||
const data = await this.getWordsFromApi(qry);
|
||||
return {
|
||||
qry,
|
||||
type: CardType.Word,
|
||||
data,
|
||||
} as CardItem;
|
||||
} else if (qry.search(/(H|G)[0-9]/i) !== -1) {
|
||||
// its a strongs lookup
|
||||
const dict = qry.substring(0, 1).search(/h/i) !== -1 ? 'heb' : 'grk';
|
||||
const strongsNumber = qry.substring(1, qry.length);
|
||||
return await this.getStrongsCard(strongsNumber, dict);
|
||||
} else {
|
||||
// its a verse reference.
|
||||
if (qry !== '') {
|
||||
const myref = new BibleReference(qry.trim());
|
||||
const data = await this.getPassageFromApi(myref.section);
|
||||
return {
|
||||
qry,
|
||||
type: CardType.Passage,
|
||||
data,
|
||||
} as CardItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#region Saved Pages
|
||||
|
||||
getSavedPage(pageid: string) {
|
||||
async getSavedPage(pageid: string) {
|
||||
const page = this.getState().savedPages.value.find((o) => o.id === pageid);
|
||||
const cards = [];
|
||||
|
||||
for (const ref of page.queries) {
|
||||
cards.push(await this.getCardByQuery(ref.qry));
|
||||
}
|
||||
|
||||
this.dispatch({
|
||||
type: 'GET_SAVED_PAGE',
|
||||
pageId: pageid,
|
||||
type: 'UPDATE_CARDS',
|
||||
cards,
|
||||
});
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user