diff --git a/app/db/src/app/common/card-operations.spec.ts b/app/db/src/app/common/card-operations.spec.ts new file mode 100644 index 00000000..54f957e7 --- /dev/null +++ b/app/db/src/app/common/card-operations.spec.ts @@ -0,0 +1,56 @@ +import { maybeMergeCards, mergeCardList } from './card-operations'; +import { BibleReference, Overlap } from './bible-reference'; + +describe('Card Merging', () => { + it('Should merge two equal cards', () => { + let cardList = [ + { type: 'Passage', qry: 'Genesis 1:1', data: null, dict: '' }, + { type: 'Passage', qry: 'Genesis 1:1', data: null, dict: '' }, + ]; + for (const strat of [Overlap.Equal, Overlap.Subset]) { + let merged = mergeCardList(cardList, strat); + expect(merged.length).toBe(1); + expect(merged[0].qry).toEqual(new BibleReference('Genesis 1:1').toString()); + } + }); + + it('Should merge two equal cards with one in the middle', () => { + let cardList = [ + { type: 'Passage', qry: 'Genesis 1:1', data: null, dict: '' }, + { type: 'Passage', qry: 'Genesis 1:2', data: null, dict: '' }, + { type: 'Passage', qry: 'Genesis 1:1', data: null, dict: '' }, + ]; + for (const strat of [Overlap.Equal, Overlap.Subset]) { + let merged = mergeCardList(cardList, strat); + expect(merged.length).toBe(2); + expect(merged[0].qry).toEqual(new BibleReference('Genesis 1:1').toString()); + expect(merged[1].qry).toEqual(new BibleReference('Genesis 1:2').toString()); + } + }); + + it('Should merge two intersecting cards', () => { + let cardList = [ + { type: 'Passage', qry: 'Genesis 1:1-2', data: null, dict: '' }, + { type: 'Passage', qry: 'Genesis 1:1', data: null, dict: '' }, + ]; + for (const strat of [Overlap.Intersect, Overlap.Subset]) { + let merged = mergeCardList(cardList, strat); + expect(merged.length).toBe(1); + expect(merged[0].qry).toEqual(new BibleReference('Genesis 1:1 - 2').toString()); + } + }); + + it('Should merge two intersecting cards with one in the middle', () => { + let cardList = [ + { type: 'Passage', qry: 'Genesis 1:1-2', data: null, dict: '' }, + { type: 'Passage', qry: 'Genesis 1:4', data: null, dict: '' }, + { type: 'Passage', qry: 'Genesis 1:1', data: null, dict: '' }, + ]; + for (const strat of [Overlap.Intersect, Overlap.Subset]) { + let merged = mergeCardList(cardList, strat); + expect(merged.length).toBe(2); + expect(merged[0].qry).toEqual(new BibleReference('Genesis 1:1 - 2').toString()); + expect(merged[1].qry).toEqual(new BibleReference('Genesis 1:4').toString()); + } + }); +}); diff --git a/app/db/src/app/common/card-operations.ts b/app/db/src/app/common/card-operations.ts new file mode 100644 index 00000000..fcc4a4cd --- /dev/null +++ b/app/db/src/app/common/card-operations.ts @@ -0,0 +1,55 @@ +import { BibleReference, Overlap } from './bible-reference'; +import { CardItem } from '../models/app-state'; + +export function maybeMergeCards(leftCard: CardItem, rightCard: CardItem, strategy: Overlap): CardItem | null { + if (leftCard.type === rightCard.type) { + switch (strategy) { + case Overlap.Equal: + if (leftCard.qry === rightCard.qry) { + return { ...leftCard }; + } + break; + case Overlap.Intersect: + case Overlap.Subset: + if (leftCard.type === 'Passage') { + const leftRef = new BibleReference(leftCard.qry); + const rightRef = new BibleReference(rightCard.qry); + let mergedRef = BibleReference.mergeReference(leftRef, rightRef, strategy); + if (mergedRef !== null) { + return { + ...leftCard, + qry: mergedRef.toString(), + }; + } + } + break; + } + } + return null; +} + +export function mergeCardList(cardList: CardItem[], strategy: Overlap) { + if (strategy == Overlap.None || cardList.length === 0) { + return [...cardList]; + } + let cardListCopy = [...cardList]; + let removals = []; + for (const cardIdx in cardListCopy) { + if (cardIdx in removals) { + continue; + } + for (let compareIdx = Number(cardIdx) + 1; compareIdx < cardListCopy.length; compareIdx++) { + const leftCard = cardListCopy[cardIdx]; + const rightCard = cardListCopy[compareIdx]; + let mergedCard = maybeMergeCards(leftCard, rightCard, strategy); + if (mergedCard != null) { + cardListCopy[cardIdx] = mergedCard; + removals.push(compareIdx); + } + } + } + for (const idx of removals) { + cardListCopy.splice(idx, 1); + } + return cardListCopy; +} diff --git a/app/db/src/app/models/app-state.ts b/app/db/src/app/models/app-state.ts index 19a3b2d0..09b0724c 100644 --- a/app/db/src/app/models/app-state.ts +++ b/app/db/src/app/models/app-state.ts @@ -3,12 +3,14 @@ import { NoteItem } from './note-state'; import { BiblePassageResult } from './passage-state'; import { StrongsResult } from './strongs-state'; import { WordLookupResult } from './words-state'; +import { Overlap } from '../common/bible-reference'; export interface AppState { readonly currentSavedPage: SavedPage; readonly savedPages: IStorable; readonly notes: IStorable; readonly displaySettings: IStorable; + readonly pageSettings: IStorable; readonly savedPagesLoaded: boolean; readonly mainPages: readonly Page[]; readonly cards: readonly CardItem[]; @@ -38,6 +40,10 @@ export interface CardIcons { readonly note: string; } +export interface PageSettings { + readonly mergeStrategy: Overlap; +} + export interface DisplaySettings { readonly showStrongsAsModal: boolean; @@ -72,6 +78,7 @@ export interface SavedPage { export interface CardItem { readonly qry: string; readonly data: Data; + // TODO(jwall): This should probably be an enum readonly type: string; readonly dict: string; } diff --git a/app/db/src/app/services/app.service.ts b/app/db/src/app/services/app.service.ts index 2a6cf7e7..06b2492e 100644 --- a/app/db/src/app/services/app.service.ts +++ b/app/db/src/app/services/app.service.ts @@ -1,7 +1,7 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { AppState, SavedPage, Error, CardItem, DisplaySettings, User } from '../models/app-state'; -import { Section, BibleReference } from '../common/bible-reference'; +import { Section, BibleReference, Overlap } from '../common/bible-reference'; import { PageTitles, PageIcons } from '../constants'; import { createStateService } from '../common/state-service'; import { UUID } from 'angular2-uuid'; @@ -68,6 +68,12 @@ const initialState: AppState = { syncCardsAcrossDevices: false, }, }, + pageSettings: { + createdOn: new Date(0).toISOString(), + value: { + mergeStrategy: Overlap.Subset, + }, + }, cardIcons: { words: 'font_download', passage: 'menu_book', @@ -245,7 +251,7 @@ function reducer(state: AppState, action: AppAction): AppState { { id: state.currentSavedPage.id, title: state.currentSavedPage.title, - queries: [...state.cards], + queries: [...state.cards], // need a function that processes the cards to merge references. }, ]); @@ -261,7 +267,7 @@ function reducer(state: AppState, action: AppAction): AppState { // create a new saved page object title: action.title, id: UUID.UUID().toString(), - queries: [...state.cards], + queries: [...state.cards], // need a function that processes the cards to merge references. }, ]);