Compare commits

...

13 Commits

Author SHA1 Message Date
Jason Wall
31c9da233a Merge branch '26-bug-fixes-and-linting-issues' into 'main'
Resolve "Bug Fixes and Linting Issues"

Closes #26

See merge request walljm/dynamicbible!27
2024-03-09 20:53:59 +00:00
Jason Wall
d9891f6e97 new dependency tree 2024-03-09 15:53:56 -05:00
Jason Wall
098d2527eb remove very old and apparently unused dependency 2024-03-09 15:46:12 -05:00
Jason Wall
0efe9c06f2 lets see if this fixes the lock file situation 2024-03-09 15:29:31 -05:00
Jason Wall
058ac594f3 update package lock 2024-03-09 14:45:21 -05:00
Jason Wall
11c9a798d4 Sort the imports :) 2024-03-09 14:34:17 -05:00
Jason Wall
8ae4cd7046 add regex optimization eslint 2024-03-09 14:30:15 -05:00
Jason Wall
e22668640e removed unused imports and variables 2024-03-09 14:19:13 -05:00
Jason Wall
eff29f6f92 Only show tabs if there are more than one 2024-03-09 14:03:08 -05:00
Jason Wall
02cef20cd0 Fix Titles of modals so they use the whole width 2024-03-09 13:59:04 -05:00
Jason Wall
3a1f5639c6 Support tabs in display of multiple strongs numbers 2024-03-09 13:52:34 -05:00
Jason Wall
838bfe1491 couple of minor formatting issues 2024-03-09 13:06:00 -05:00
Jason Wall
f35b5d4902 remove redux. you don't need it cause you're pretty much just using RXJS. 2024-03-09 13:05:45 -05:00
46 changed files with 4790 additions and 2833 deletions

View File

@ -3,10 +3,11 @@
"ignorePatterns": ["projects/**/*"], "ignorePatterns": ["projects/**/*"],
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"parserOptions": { "parserOptions": {
"project": "./tsconfig.json", // <-- Point to your project's "tsconfig.json" or create a new one.
"ecmaVersion": 2020, "ecmaVersion": 2020,
"sourceType": "module", "sourceType": "module"
"project": "./tsconfig.json" // <-- Point to your project's "tsconfig.json" or create a new one.
}, },
"plugins": ["unused-imports", "optimize-regex", "simple-import-sort"],
"overrides": [ "overrides": [
{ {
"files": ["*.ts"], "files": ["*.ts"],
@ -35,7 +36,21 @@
"prefix": "app", "prefix": "app",
"style": "kebab-case" "style": "kebab-case"
} }
] ],
"no-unused-vars": "off", // or "@typescript-eslint/no-unused-vars": "off",
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"warn",
{
"vars": "all",
"varsIgnorePattern": "^_",
"args": "after-used",
"argsIgnorePattern": "^_"
}
],
"optimize-regex/optimize-regex": "warn",
"simple-import-sort/imports": "warn",
"simple-import-sort/exports": "warn"
} }
}, },
{ {

View File

@ -177,8 +177,7 @@
"schematicCollections": [ "schematicCollections": [
"@cypress/schematic", "@cypress/schematic",
"@angular-eslint/schematics", "@angular-eslint/schematics",
"@angular-eslint/schematics",
"@schematics/angular" "@schematics/angular"
] ]
} }
} }

4197
src/dependency-tree.txt Normal file

File diff suppressed because it is too large Load Diff

2491
src/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -32,13 +32,10 @@
"@capacitor/ios": "^5.7.0", "@capacitor/ios": "^5.7.0",
"@codetrix-studio/capacitor-google-auth": "^3.4.0-rc.0", "@codetrix-studio/capacitor-google-auth": "^3.4.0-rc.0",
"@ngx-pwa/local-storage": "^17.0.0", "@ngx-pwa/local-storage": "^17.0.0",
"@reduxjs/toolkit": "^2.2.1",
"angular2-uuid": "^1.1.1", "angular2-uuid": "^1.1.1",
"component": "^1.1.0",
"firebase": "^10.8.0", "firebase": "^10.8.0",
"marked": "^9.0.0", "marked": "^9.0.0",
"ngx-markdown": "^17.1.1", "ngx-markdown": "^17.1.1",
"redux": "^5.0.1",
"reselect": "^5.1.0", "reselect": "^5.1.0",
"rxjs": "^7.8.1", "rxjs": "^7.8.1",
"tslib": "^2.6.2", "tslib": "^2.6.2",
@ -64,6 +61,9 @@
"cypress": "latest", "cypress": "latest",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-plugin-deprecation": "^2.0.0", "eslint-plugin-deprecation": "^2.0.0",
"eslint-plugin-optimize-regex": "^1.2.1",
"eslint-plugin-simple-import-sort": "^12.0.0",
"eslint-plugin-unused-imports": "^3.1.0",
"firebase-tools": "^13.4.0", "firebase-tools": "^13.4.0",
"fuzzy": "^0.1.3", "fuzzy": "^0.1.3",
"inquirer": "^9.2.15", "inquirer": "^9.2.15",

View File

@ -1,8 +1,9 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router'; import { RouterModule,Routes } from '@angular/router';
import { SearchPageComponent } from './pages/search/search.page';
import { SavedPagesAdminPageComponent } from './pages/saved-pages-admin/saved-pages-admin.page';
import { NotesAdminPageComponent } from './pages/notes-admin/notes-admin.page'; import { NotesAdminPageComponent } from './pages/notes-admin/notes-admin.page';
import { SavedPagesAdminPageComponent } from './pages/saved-pages-admin/saved-pages-admin.page';
import { SearchPageComponent } from './pages/search/search.page';
const routes: Routes = [ const routes: Routes = [
{ {

View File

@ -16,7 +16,7 @@
> >
<a mat-list-item (click)="showHelp()" <a mat-list-item (click)="showHelp()"
><mat-icon color="accenovert">help</mat-icon>Help</a ><mat-icon color="accenovert">help</mat-icon> Help</a
> >
</mat-nav-list> </mat-nav-list>

View File

@ -1,15 +1,16 @@
import { Component, ViewChild, AfterViewInit, ChangeDetectorRef } from '@angular/core'; import { BreakpointObserver,Breakpoints } from '@angular/cdk/layout';
import { AfterViewInit, ChangeDetectorRef,Component, ViewChild } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatSidenav } from '@angular/material/sidenav';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Observable } from 'rxjs';
import { map, shareReplay } from 'rxjs/operators';
import { SubscriberBase } from './common/subscriber-base';
import { HelpModalComponent } from './components/help-modal/help-modal.component';
import { AppService } from './services/app.service';
import { NavService } from './services/nav.service'; import { NavService } from './services/nav.service';
import { StorageService } from './services/storage.service'; import { StorageService } from './services/storage.service';
import { Observable } from 'rxjs';
import { Breakpoints, BreakpointObserver } from '@angular/cdk/layout';
import { map, shareReplay } from 'rxjs/operators';
import { MatSidenav } from '@angular/material/sidenav';
import { SubscriberBase } from './common/subscriber-base';
import { MatSnackBar } from '@angular/material/snack-bar';
import { HelpModalComponent } from './components/help-modal/help-modal.component';
import { MatDialog } from '@angular/material/dialog';
import { AppService } from './services/app.service';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',

View File

@ -1,72 +1,63 @@
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { ClipboardModule } from '@angular/cdk/clipboard';
import { HttpClientModule } from '@angular/common/http';
import { BrowserModule } from '@angular/platform-browser';
import { APP_ID, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MARKED_OPTIONS, MarkdownModule } from 'ngx-markdown';
import { AngularFireModule } from '@angular/fire/compat';
import { FirebaseConfig } from './constants';
import { DragDropModule } from '@angular/cdk/drag-drop'; import { DragDropModule } from '@angular/cdk/drag-drop';
import { HttpClientModule } from '@angular/common/http';
import { APP_ID, NgModule } from '@angular/core';
import { AngularFireModule } from '@angular/fire/compat';
import { AngularFireAuthModule } from '@angular/fire/compat/auth'; import { AngularFireAuthModule } from '@angular/fire/compat/auth';
import { AngularFireDatabaseModule } from '@angular/fire/compat/database'; import { AngularFireDatabaseModule } from '@angular/fire/compat/database';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatButtonModule } from '@angular/material/button';
import { MatInputModule } from '@angular/material/input';
import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatRadioModule } from '@angular/material/radio';
import { MatSelectModule } from '@angular/material/select';
import { MatSliderModule } from '@angular/material/slider';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatMenuModule } from '@angular/material/menu';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatListModule } from '@angular/material/list';
import { MatCardModule } from '@angular/material/card';
import { MatStepperModule } from '@angular/material/stepper';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatIconModule } from '@angular/material/icon';
import { MatDialogModule } from '@angular/material/dialog';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatBadgeModule } from '@angular/material/badge'; import { MatBadgeModule } from '@angular/material/badge';
import { MatBottomSheetModule } from '@angular/material/bottom-sheet'; import { MatBottomSheetModule } from '@angular/material/bottom-sheet';
import { MatDividerModule } from '@angular/material/divider'; import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatCardModule } from '@angular/material/card';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatChipsModule } from '@angular/material/chips'; import { MatChipsModule } from '@angular/material/chips';
import { MatNativeDateModule, MatRippleModule } from '@angular/material/core'; import { MatNativeDateModule, MatRippleModule } from '@angular/material/core';
import { ClipboardModule } from '@angular/cdk/clipboard'; import { MatDialogModule } from '@angular/material/dialog';
import { MatDividerModule } from '@angular/material/divider';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatListModule } from '@angular/material/list';
import { MatMenuModule } from '@angular/material/menu';
import { MatRadioModule } from '@angular/material/radio';
import { MatSelectModule } from '@angular/material/select';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatSliderModule } from '@angular/material/slider';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatStepperModule } from '@angular/material/stepper';
import { MatTabsModule } from '@angular/material/tabs';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatTooltipModule } from '@angular/material/tooltip';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MarkdownModule,MARKED_OPTIONS } from 'ngx-markdown';
import { MarkedOptions, MarkedRenderer } from 'ngx-markdown';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { NotesAdminPageComponent } from './pages/notes-admin/notes-admin.page'; import { AddToPageModalComponent } from './components/add-to-page-modal/add-to-page-modal.component';
import { SavedPagesAdminPageComponent } from './pages/saved-pages-admin/saved-pages-admin.page';
import { SavedPageCardComponent } from './components/saved-page-card/saved-page-card.component';
import { HelpModalComponent } from './components/help-modal/help-modal.component'; import { HelpModalComponent } from './components/help-modal/help-modal.component';
import { NoteEditModalComponent } from './components/note/edit-modal/note-edit-modal.component';
import { SearchPageComponent } from './pages/search/search.page'; import { NoteCardComponent } from './components/note/note-card.component';
import { OkCancelModalComponent } from './components/ok-cancel-modal/ok-cancel-modal.component'; import { OkCancelModalComponent } from './components/ok-cancel-modal/ok-cancel-modal.component';
import { SettingsComponent } from './components/settings/settings.component'; import { PageEditModalComponent } from './components/page-edit-modal/page-edit-modal.component';
import { PassageCardComponent } from './components/passage/passage-card.component'; import { PassageCardComponent } from './components/passage/passage-card.component';
import { StrongsComponent } from './components/strongs/strongs.component'; import { SavedPageCardComponent } from './components/saved-page-card/saved-page-card.component';
import { SettingsComponent } from './components/settings/settings.component';
import { StrongsCardComponent } from './components/strongs/card/strongs-card.component'; import { StrongsCardComponent } from './components/strongs/card/strongs-card.component';
import { StrongsModalComponent } from './components/strongs/modal/strongs-modal.component'; import { StrongsModalComponent } from './components/strongs/modal/strongs-modal.component';
import { WordsCardComponent } from './components/words/words-card.component'; import { StrongsComponent } from './components/strongs/strongs.component';
import { NoteCardComponent } from './components/note/note-card.component';
import { PageEditModalComponent } from './components/page-edit-modal/page-edit-modal.component';
import { NoteEditModalComponent } from './components/note/edit-modal/note-edit-modal.component';
import { VersePickerModalComponent } from './components/verse-picker-modal/verse-picker-modal.component'; import { VersePickerModalComponent } from './components/verse-picker-modal/verse-picker-modal.component';
import { AddToPageModalComponent } from './components/add-to-page-modal/add-to-page-modal.component'; import { WordsCardComponent } from './components/words/words-card.component';
import { FirebaseConfig } from './constants';
import { MarkedOptions, MarkedRenderer } from 'ngx-markdown'; import { NotesAdminPageComponent } from './pages/notes-admin/notes-admin.page';
import { SavedPagesAdminPageComponent } from './pages/saved-pages-admin/saved-pages-admin.page';
import { SearchPageComponent } from './pages/search/search.page';
// function that returns `MarkedOptions` with renderer override // function that returns `MarkedOptions` with renderer override
export function markedOptionsFactory(): MarkedOptions { export function markedOptionsFactory(): MarkedOptions {
@ -153,6 +144,7 @@ export function markedOptionsFactory(): MarkedOptions {
MatSnackBarModule, MatSnackBarModule,
MatTooltipModule, MatTooltipModule,
MatFormFieldModule, MatFormFieldModule,
MatTabsModule,
ClipboardModule, ClipboardModule,
], ],
providers: [{ provide: APP_ID, useValue: 'ng-cli-universal' }], providers: [{ provide: APP_ID, useValue: 'ng-cli-universal' }],

View File

@ -1,4 +1,4 @@
import { moveItemUpOrDown, moveItem } from './array-operations'; import { moveItem,moveItemUpOrDown } from './array-operations';
import { MoveDirection } from './move-direction'; import { MoveDirection } from './move-direction';
describe('Array Movement', () => { describe('Array Movement', () => {

View File

@ -1,6 +1,7 @@
import { MoveDirection } from './move-direction';
import { moveItemInArray } from '@angular/cdk/drag-drop'; import { moveItemInArray } from '@angular/cdk/drag-drop';
import { MoveDirection } from './move-direction';
/** /**
* Moves an item up (1 index towards 0) or down (1 index away from 0) immutably, returning a new array as the value. * Moves an item up (1 index towards 0) or down (1 index away from 0) immutably, returning a new array as the value.
* @param items Array in which to move the item. * @param items Array in which to move the item.

View File

@ -924,7 +924,7 @@ export class BibleReference {
if (this.ref.length === 0) { if (this.ref.length === 0) {
return; return;
} }
this.parseBook(false); this.parseBook();
this.parseChapter(false); this.parseChapter(false);
const foundFirstVerse = this.ref.search(/:.*-/) !== -1; const foundFirstVerse = this.ref.search(/:.*-/) !== -1;
@ -932,7 +932,7 @@ export class BibleReference {
this.maybeParseRangeSep(); this.maybeParseRangeSep();
const foundSecondBook = this.ref.search(/\w\s+\d/i) !== -1; const foundSecondBook = this.ref.search(/\w\s+\d/i) !== -1;
this.maybeParseBook(true); this.maybeParseBook();
this.maybeParseChapterOrVerse(foundSecondBook, foundFirstVerse, true); this.maybeParseChapterOrVerse(foundSecondBook, foundFirstVerse, true);
this.maybeParseVerse(true); this.maybeParseVerse(true);
} }
@ -958,7 +958,7 @@ export class BibleReference {
} }
} }
private parseBook(isEnd?: boolean) { private parseBook() {
this.ref = this.ref.toLowerCase().trim(); this.ref = this.ref.toLowerCase().trim();
let fbook = this.ref.substring(0, this.ref.search(/\w\s+\d/i) + 1); let fbook = this.ref.substring(0, this.ref.search(/\w\s+\d/i) + 1);
@ -1039,10 +1039,10 @@ export class BibleReference {
} }
} }
private maybeParseBook(isEnd: boolean) { private maybeParseBook() {
return this.maybeDo(() => { return this.maybeDo(() => {
if (this.ref.search(/\w\s+\d/i) !== -1) { if (this.ref.search(/\w\s+\d/i) !== -1) {
this.parseBook(isEnd); this.parseBook();
} }
}); });
} }

View File

@ -1,7 +1,7 @@
import { CardType, CardItem } from '../models/card-state'; import { CardItem,CardType } from '../models/card-state';
import { NoteItem } from '../models/note-state'; import { NoteItem } from '../models/note-state';
import { getCardCacheKey, removeFromCardCache,updateInCardCache } from './card-cache-operations';
import { HashTable } from './hashtable'; import { HashTable } from './hashtable';
import { updateInCardCache, getCardCacheKey, removeFromCardCache } from './card-cache-operations';
describe('Card Cache', () => { describe('Card Cache', () => {
it('updateCache', () => { it('updateCache', () => {
@ -48,16 +48,6 @@ describe('Card Cache', () => {
type: CardType.Passage, type: CardType.Passage,
data: null, data: null,
}; };
const card2: CardItem = {
qry: 'jason',
type: CardType.Passage,
data: {
id: 'adsf',
xref: '',
title: 'adsf',
content: '',
} as NoteItem,
};
const card3: CardItem = { const card3: CardItem = {
qry: 'jason3', qry: 'jason3',
type: CardType.Passage, type: CardType.Passage,

View File

@ -1,6 +1,6 @@
import { mergeCardList } from './card-operations'; import { CardItem,CardType } from '../models/card-state';
import { BibleReference, Overlap } from './bible-reference'; import { BibleReference, Overlap } from './bible-reference';
import { CardType, CardItem } from '../models/card-state'; import { mergeCardList } from './card-operations';
describe('Card Merging', () => { describe('Card Merging', () => {
it('Should merge two equal reference cards', () => { it('Should merge two equal reference cards', () => {

View File

@ -1,5 +1,5 @@
import { BibleReference, Overlap } from './bible-reference';
import { CardType, DataReference } from '../models/card-state'; import { CardType, DataReference } from '../models/card-state';
import { BibleReference, Overlap } from './bible-reference';
export function maybeMergeCards( export function maybeMergeCards(
leftCard: DataReference, leftCard: DataReference,

View File

@ -1,103 +1,6 @@
import { Store } from 'redux';
import { configureStore } from '@reduxjs/toolkit';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { distinctUntilChanged, map } from 'rxjs/operators'; import { distinctUntilChanged, map } from 'rxjs/operators';
/**
* A base class from which to extend a service to provide predictable state to components. You should not extend this
* class directly; instead, use the {@link createStateService} function to create a base class specific to your service.
* @see {@link createStateService}
*/
class StateService<TState, TAction extends { type: string }> {
/** An observable that provides the entire state managed by the service. */
public readonly state$: Observable<TState>;
private readonly store: Store<TState, TAction>;
private readonly internalState$: BehaviorSubject<TState>;
protected constructor(reducer: (state: TState, action: TAction) => TState, initialState: TState) {
this.store = configureStore({
reducer: reducer,
preloadedState: initialState,
});
this.internalState$ = new BehaviorSubject<TState>(initialState as TState);
// BehaviorSubject.asObservable returns a new object, so hold onto it to avoid unnecessary allocations
this.state$ = this.internalState$.asObservable();
this.store.subscribe(() => this.internalState$.next(this.store.getState()));
}
/**
* Creates an observable that provides data derived from the state managed by the service.
* @param selector A selector that maps the state to the derived data.
*/
public select<TDerived>(selector: (state: TState) => TDerived): Observable<TDerived> {
return this.state$.pipe(map(selector), distinctUntilChanged());
}
/**
* Gets the current state managed by the service. **You should only use the current state to validate operations or
* help to construct actions to be dispatched.** Try not to expose any state retrieved using this method outside the
* derived service class.
*/
protected getState(): TState {
return this.internalState$.value;
}
/**
* Dispatches an action to the underlying Redux store.
* @param action The action to dispatch.
*/
protected dispatch(action: TAction) {
this.store.dispatch(action);
}
}
/**
* Creates a deeply-immutable type from the provided type. Objects' properties will be marked as `readonly`, array types
* will be replaced with {@link ReadonlyArray}, {@link Map} types will be replaced with {@link ReadonlyMap}, and
* {@link Set} types will be replaced with {@link ReadonlySet}.
*/
export type Immutable<T> = T extends undefined | null | boolean | string | number
? T
: T extends Array<infer U>
? ImmutableArray<U>
: T extends Map<infer K, infer V>
? ImmutableMap<K, V>
: T extends Set<infer M>
? ImmutableSet<M>
: { readonly [N in keyof T]: Immutable<T[N]> };
interface ImmutableArray<T> extends ReadonlyArray<Immutable<T>> {}
interface ImmutableMap<K, V> extends ReadonlyMap<Immutable<K>, Immutable<V>> {}
interface ImmutableSet<T> extends ReadonlySet<Immutable<T>> {}
// The below type definition is simpler, but it only works with TypeScript 3.7 and above. Swap this in when we upgrade!
// export type Immutable<T> = T extends undefined | null | boolean | string | number
// ? T
// : T extends Array<infer U>
// ? ReadonlyArray<Immutable<U>>
// : T extends Map<infer K, infer V>
// ? ReadonlyMap<Immutable<K>, Immutable<V>>
// : T extends Set<infer M>
// ? ReadonlySet<Immutable<M>>
// : { readonly [N in keyof T]: Immutable<T[N]> };
type IfEquals<X, Y, A = X, B = never> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? A : B;
type IfImmutable<TState extends {}, TImmutable = TState, TMutable = never> = IfEquals<
TState,
{ readonly [K in keyof TState]: Immutable<TState[K]> },
TImmutable,
TMutable
>;
// eslint-disable-next-line
type YourStateTypeNeedsToBeImmutable<TState> = {};
/** /**
* Creates a base class from which to extend a service to provide predictable state to components. * Creates a base class from which to extend a service to provide predictable state to components.
* *
@ -116,20 +19,75 @@ type YourStateTypeNeedsToBeImmutable<TState> = {};
* @param reducer A function that takes the previous state and the dispatched action and returns the new state. * @param reducer A function that takes the previous state and the dispatched action and returns the new state.
* @param initialState The initial state of the service. * @param initialState The initial state of the service.
*/ */
export function createStateService<TState, TAction extends { type: string }>( export function createReducingService<TState>(initialState: TState) {
reducer: (state: TState, action: TAction) => TState, const stateServiceClass = class extends ReducingService<TState> {
initialState: TState
) {
const stateServiceClass = class extends StateService<TState, TAction> {
constructor() { constructor() {
super(reducer, initialState); super(initialState);
} }
}; };
return stateServiceClass as IfImmutable<TState, typeof stateServiceClass, YourStateTypeNeedsToBeImmutable<TState>>; return stateServiceClass as IfImmutable<TState, typeof stateServiceClass, YourStateTypeNeedsToBeImmutable<TState>>;
} }
export interface IStateAction<TState, TAction> { export interface IReducingAction<TState> {
type: TAction;
handle: (state: TState) => TState; handle: (state: TState) => TState;
} }
/**
* Creates a deeply-immutable type from the provided type. Objects' properties will be marked as `readonly`, array types
* will be replaced with {@link ReadonlyArray}, {@link Map} types will be replaced with {@link ReadonlyMap}, and
* {@link Set} types will be replaced with {@link ReadonlySet}.
*/
export type Immutable<T> = T extends undefined | null | boolean | string | number
? T
: T extends Array<infer U>
? ReadonlyArray<Immutable<U>>
: T extends Map<infer K, infer V>
? ReadonlyMap<Immutable<K>, Immutable<V>>
: T extends Set<infer M>
? ReadonlySet<Immutable<M>>
: { readonly [N in keyof T]: Immutable<T[N]> };
type IfEquals<X, Y, A = X, B = never> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? A : B;
type IfImmutable<TState extends {}, TImmutable = TState, TMutable = never> = IfEquals<
TState,
{ readonly [K in keyof TState]: Immutable<TState[K]> },
TImmutable,
TMutable
>;
// eslint-disable-next-line
type YourStateTypeNeedsToBeImmutable<TState> = {};
class ReducingService<TState> {
private internalState$: BehaviorSubject<TState>;
constructor(private initialState: TState) {
this.internalState$ = new BehaviorSubject(initialState);
}
// expose as observable.
public get state$() {
return this.internalState$.asObservable();
}
protected dispatch(action: IReducingAction<TState>) {
this.internalState$.next(action.handle(this.internalState$.value));
}
/**
* Gets the current state managed by the service. **You should only use the current state to validate operations or
* help to construct actions to be dispatched.** Try not to expose any state retrieved using this method outside the
* derived service class.
*/
protected getState(): TState {
return this.internalState$.value;
}
/**
* Creates an observable that provides data derived from the state managed by the service.
* @param selector A selector that maps the state to the derived data.
*/
public select<TDerived>(selector: (state: TState) => TDerived): Observable<TDerived> {
return this.internalState$.pipe(map(selector), distinctUntilChanged());
}
}

View File

@ -1,4 +1,4 @@
import { OnDestroy, Injectable } from '@angular/core'; import { Injectable,OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
@Injectable({ @Injectable({

View File

@ -1,9 +1,7 @@
<div mat-dialog-title> <mat-toolbar>
<mat-toolbar> <mat-icon>save</mat-icon>
<mat-icon>save</mat-icon> <div class="title">Add Card to Saved Page</div>
<div class="title">Add Card to Saved Page</div> </mat-toolbar>
</mat-toolbar>
</div>
<mat-dialog-content class="content"> <mat-dialog-content class="content">
<div class="page-list"> <div class="page-list">
<mat-selection-list #pageList> <mat-selection-list #pageList>
@ -26,7 +24,6 @@
><button ><button
class="page-add-button" class="page-add-button"
mat-icon-button mat-icon-button
mat-button
[disableRipple]="true" [disableRipple]="true"
(click)="addPage()" (click)="addPage()"
> >

View File

@ -1,10 +1,11 @@
import { Component, Inject, ViewChild } from '@angular/core'; import { Component, Inject, ViewChild } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { MatSelectionList } from '@angular/material/list';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { CardItem } from '../../models/card-state'; import { MAT_DIALOG_DATA,MatDialogRef } from '@angular/material/dialog';
import { MatSelectionList } from '@angular/material/list';
import { AppService } from 'src/app/services/app.service'; import { AppService } from 'src/app/services/app.service';
import { CardItem } from '../../models/card-state';
@Component({ @Component({
selector: 'app-add-to-page-modal-modal', selector: 'app-add-to-page-modal-modal',
templateUrl: 'add-to-page-modal.component.html', templateUrl: 'add-to-page-modal.component.html',

View File

@ -1,12 +1,13 @@
import { EventEmitter, Output, Input, ElementRef, Component } from '@angular/core'; import { Clipboard } from '@angular/cdk/clipboard';
import { Observable } from 'rxjs'; import { Component,ElementRef, EventEmitter, Input, Output } from '@angular/core';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { SubscriberBase } from '../common/subscriber-base'; import { Observable } from 'rxjs';
import { MoveDirection } from '../common/move-direction'; import { MoveDirection } from '../common/move-direction';
import { SubscriberBase } from '../common/subscriber-base';
import { AddToPageModalComponent } from '../components/add-to-page-modal/add-to-page-modal.component'; import { AddToPageModalComponent } from '../components/add-to-page-modal/add-to-page-modal.component';
import { CardItem } from '../models/card-state'; import { CardItem } from '../models/card-state';
import { AppService } from '../services/app.service'; import { AppService } from '../services/app.service';
import { Clipboard } from '@angular/cdk/clipboard';
@Component({ @Component({
template: '', template: '',
@ -36,7 +37,7 @@ export class CardComponent extends SubscriberBase {
const attempt = () => { const attempt = () => {
const result = pending.copy(); const result = pending.copy();
if (!result && --remainingAttempts) { if (!result && --remainingAttempts) {
setTimeout(attempt); setTimeout(attempt, 10);
} else { } else {
// Remember to destroy when you're done! // Remember to destroy when you're done!
pending.destroy(); pending.destroy();

View File

@ -1,4 +1,5 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { NavService } from '../../services/nav.service'; import { NavService } from '../../services/nav.service';
@Component({ @Component({

View File

@ -1,10 +1,9 @@
<div mat-dialog-title> <mat-toolbar>
<mat-toolbar> <mat-icon>edit</mat-icon>
<mat-icon>edit</mat-icon> <div class="title">Edit: {{ data.title }}</div>
<div class="title">Edit: {{ data.title }}</div> </mat-toolbar>
</mat-toolbar>
</div>
<mat-dialog-content> <mat-dialog-content>
<br />
<form [formGroup]="noteForm"> <form [formGroup]="noteForm">
<mat-form-field class="note-title"> <mat-form-field class="note-title">
<mat-label>Title</mat-label> <mat-label>Title</mat-label>

View File

@ -1,14 +1,11 @@
import { Component, Inject } from '@angular/core';
import { UntypedFormGroup, UntypedFormBuilder } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes'; import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes';
import { Component, Inject } from '@angular/core';
import { UntypedFormBuilder,UntypedFormGroup } from '@angular/forms';
import { MatChipInputEvent } from '@angular/material/chips'; import { MatChipInputEvent } from '@angular/material/chips';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { UUID } from 'angular2-uuid'; import { UUID } from 'angular2-uuid';
import { NoteItem } from 'src/app/models/note-state';
import { CardItem } from 'src/app/models/card-state'; import { CardItem } from 'src/app/models/card-state';
import { NoteItem } from 'src/app/models/note-state';
import { AppService } from 'src/app/services/app.service'; import { AppService } from 'src/app/services/app.service';
@Component({ @Component({

View File

@ -1,10 +1,11 @@
import { Component, ViewChild, ElementRef, Input } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { Clipboard } from '@angular/cdk/clipboard'; import { Clipboard } from '@angular/cdk/clipboard';
import { CardComponent } from '../card.component'; import { Component, ElementRef, Input,ViewChild } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { BibleReference } from 'src/app/common/bible-reference'; import { BibleReference } from 'src/app/common/bible-reference';
import { NoteItem } from 'src/app/models/note-state'; import { NoteItem } from 'src/app/models/note-state';
import { AppService } from 'src/app/services/app.service'; import { AppService } from 'src/app/services/app.service';
import { CardComponent } from '../card.component';
import { OkCancelModalComponent, OkCancelResult } from '../ok-cancel-modal/ok-cancel-modal.component'; import { OkCancelModalComponent, OkCancelResult } from '../ok-cancel-modal/ok-cancel-modal.component';
import { NoteEditModalComponent } from './edit-modal/note-edit-modal.component'; import { NoteEditModalComponent } from './edit-modal/note-edit-modal.component';

View File

@ -1,9 +1,7 @@
<div mat-dialog-title> <mat-toolbar>
<mat-toolbar> <mat-icon>save</mat-icon>
<mat-icon>save</mat-icon> <div class="title">{{ dialogTitle }}</div>
<div class="title">{{ dialogTitle }}</div> </mat-toolbar>
</mat-toolbar>
</div>
<mat-dialog-content class="content"> <mat-dialog-content class="content">
<form [formGroup]="form"> <form [formGroup]="form">
<mat-form-field class="page-title"> <mat-form-field class="page-title">

View File

@ -1,6 +1,6 @@
import { Component, Inject } from '@angular/core'; import { Component, Inject } from '@angular/core';
import { UntypedFormGroup, UntypedFormBuilder } from '@angular/forms'; import { UntypedFormBuilder,UntypedFormGroup } from '@angular/forms';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { MAT_DIALOG_DATA,MatDialogRef } from '@angular/material/dialog';
import { SavedPage } from 'src/app/models/page-state'; import { SavedPage } from 'src/app/models/page-state';
import { AppService } from 'src/app/services/app.service'; import { AppService } from 'src/app/services/app.service';

View File

@ -1,13 +1,14 @@
import { Component, OnInit, ElementRef, ViewChild, ChangeDetectionStrategy } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { Clipboard } from '@angular/cdk/clipboard'; import { Clipboard } from '@angular/cdk/clipboard';
import { NoteItem } from 'src/app/models/note-state'; import { ChangeDetectionStrategy,Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { CardComponent } from 'src/app/components/card.component'; import { MatDialog } from '@angular/material/dialog';
import { BibleReference, Overlap } from 'src/app/common/bible-reference'; import { BibleReference, Overlap } from 'src/app/common/bible-reference';
import { Paragraph, BiblePassageResult } from 'src/app/models/passage-state';
import { CardItem } from 'src/app/models/card-state';
import { isNullOrUndefined } from 'src/app/common/helpers'; import { isNullOrUndefined } from 'src/app/common/helpers';
import { CardComponent } from 'src/app/components/card.component';
import { CardItem } from 'src/app/models/card-state';
import { NoteItem } from 'src/app/models/note-state';
import { BiblePassageResult,Paragraph } from 'src/app/models/passage-state';
import { AppService } from 'src/app/services/app.service'; import { AppService } from 'src/app/services/app.service';
import { StrongsModalComponent } from '../strongs/modal/strongs-modal.component'; import { StrongsModalComponent } from '../strongs/modal/strongs-modal.component';
@Component({ @Component({
@ -60,7 +61,7 @@ export class PassageCardComponent extends CardComponent implements OnInit {
} }
copy() { copy() {
const text = this.passageElement.nativeElement.innerText + ` - ${this.ref.toString()}`; const text = this.passageElement.nativeElement.innerText + ` \n - ${this.ref.toString()}`;
this.copyToClip(text); this.copyToClip(text);
} }
@ -149,16 +150,20 @@ export class PassageCardComponent extends CardComponent implements OnInit {
async openStrongs(q: string, asModal = false) { async openStrongs(q: string, asModal = false) {
const dict = (this.cardItem.data as BiblePassageResult).dict; const dict = (this.cardItem.data as BiblePassageResult).dict;
const numbers = q.split(' '); const numbers = q.split(' ');
for (const sn of numbers) {
if (asModal) { if (asModal) {
const cards: CardItem[] = [];
for (const sn of numbers) {
const card = await this.appService.getStrongsCard(sn, dict); const card = await this.appService.getStrongsCard(sn, dict);
this.dialog.open(StrongsModalComponent, { cards.push(card);
data: card,
autoFocus: 'content',
});
} else {
this.appService.getStrongs(sn, dict, this.cardItem);
} }
this.dialog.open(StrongsModalComponent, {
data: cards,
autoFocus: 'content',
});
} else {
this.appService.getStrongs(numbers, dict, this.cardItem);
} }
} }

View File

@ -1,18 +1,19 @@
import { Component, Input, OnInit } from '@angular/core';
import { CdkDragDrop } from '@angular/cdk/drag-drop'; import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { Component, Input } from '@angular/core';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { Observable } from 'rxjs';
import { SavedPage } from 'src/app/models/page-state';
import { CardIcons, CardItem, CardType, DataReference } from 'src/app/models/card-state';
import { OkCancelModalComponent, OkCancelResult } from '../ok-cancel-modal/ok-cancel-modal.component';
import { MatSnackBar } from '@angular/material/snack-bar'; import { MatSnackBar } from '@angular/material/snack-bar';
import { PageEditModalComponent } from '../page-edit-modal/page-edit-modal.component'; import { Observable } from 'rxjs';
import { getFromCardCache } from 'src/app/common/card-cache-operations';
import { HashTable } from 'src/app/common/hashtable'; import { HashTable } from 'src/app/common/hashtable';
import { SubscriberBase } from 'src/app/common/subscriber-base'; import { SubscriberBase } from 'src/app/common/subscriber-base';
import { getFromCardCache } from 'src/app/common/card-cache-operations'; import { CardIcons, CardItem, CardType, DataReference } from 'src/app/models/card-state';
import { NoteItem } from 'src/app/models/note-state'; import { NoteItem } from 'src/app/models/note-state';
import { SavedPage } from 'src/app/models/page-state';
import { AppService } from 'src/app/services/app.service'; import { AppService } from 'src/app/services/app.service';
import { OkCancelModalComponent, OkCancelResult } from '../ok-cancel-modal/ok-cancel-modal.component';
import { PageEditModalComponent } from '../page-edit-modal/page-edit-modal.component';
@Component({ @Component({
selector: 'app-saved-page-card', selector: 'app-saved-page-card',
templateUrl: 'saved-page-card.component.html', templateUrl: 'saved-page-card.component.html',

View File

@ -1,26 +1,23 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { MatSlideToggleChange } from '@angular/material/slide-toggle';
import { MatSelectChange } from '@angular/material/select';
import { MatSnackBar } from '@angular/material/snack-bar';
import { MatDialog } from '@angular/material/dialog';
import { AngularFireAuth } from '@angular/fire/compat/auth'; import { AngularFireAuth } from '@angular/fire/compat/auth';
import firebase from '@firebase/app-compat'; import { MatDialog } from '@angular/material/dialog';
import { MatSelectChange } from '@angular/material/select';
import { MatSlideToggleChange } from '@angular/material/slide-toggle';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Capacitor } from '@capacitor/core'; import { Capacitor } from '@capacitor/core';
import { GoogleAuth } from '@codetrix-studio/capacitor-google-auth' import { GoogleAuth } from '@codetrix-studio/capacitor-google-auth'
import firebase from '@firebase/app-compat';
import { SubscriberBase } from '../../common/subscriber-base'; import { Overlap } from 'src/app/common/bible-reference';
import { DisplaySettings } from 'src/app/models/app-state'; import { NoteEditModalComponent } from 'src/app/components/note/edit-modal/note-edit-modal.component';
import { PageEditModalComponent } from 'src/app/components/page-edit-modal/page-edit-modal.component';
import { CardFonts } from 'src/app/constants'; import { CardFonts } from 'src/app/constants';
import { DisplaySettings } from 'src/app/models/app-state';
import { SavedPage } from 'src/app/models/page-state';
import { AppService } from 'src/app/services/app.service';
import { NavService } from 'src/app/services/nav.service'; import { NavService } from 'src/app/services/nav.service';
import { SubscriberBase } from '../../common/subscriber-base';
import { OkCancelModalComponent, OkCancelResult } from '../ok-cancel-modal/ok-cancel-modal.component'; import { OkCancelModalComponent, OkCancelResult } from '../ok-cancel-modal/ok-cancel-modal.component';
import { PageEditModalComponent } from 'src/app/components/page-edit-modal/page-edit-modal.component';
import { NoteEditModalComponent } from 'src/app/components/note/edit-modal/note-edit-modal.component';
import { SavedPage } from 'src/app/models/page-state';
import { Overlap } from 'src/app/common/bible-reference';
import { AppService } from 'src/app/services/app.service';
@Component({ @Component({
selector: 'app-settings', selector: 'app-settings',
@ -82,7 +79,7 @@ export class SettingsComponent extends SubscriberBase {
const credential = firebase.auth.GoogleAuthProvider.credential(googleUser.authentication.idToken); const credential = firebase.auth.GoogleAuthProvider.credential(googleUser.authentication.idToken);
(await this.authService.app).auth().signInWithCredential(credential); (await this.authService.app).auth().signInWithCredential(credential);
} else { } else {
this.authService.signInWithPopup(new firebase.auth.GoogleAuthProvider()).then((cred) => { this.authService.signInWithPopup(new firebase.auth.GoogleAuthProvider()).then(() => {
console.log('Authenticated.'); console.log('Authenticated.');
}); });
} }

View File

@ -1,11 +1,12 @@
import { Component, ElementRef, ViewChild, ChangeDetectionStrategy } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { Clipboard } from '@angular/cdk/clipboard'; import { Clipboard } from '@angular/cdk/clipboard';
import { StrongsModalComponent } from '../modal/strongs-modal.component'; import { ChangeDetectionStrategy, Component, ElementRef, Input,ViewChild } from '@angular/core';
import { CardComponent } from 'src/app/components/card.component'; import { MatDialog } from '@angular/material/dialog';
import { BibleReference } from 'src/app/common/bible-reference'; import { BibleReference } from 'src/app/common/bible-reference';
import { AppService } from 'src/app/services/app.service'; import { CardComponent } from 'src/app/components/card.component';
import { StrongsResult } from 'src/app/models/strongs-state'; import { StrongsResult } from 'src/app/models/strongs-state';
import { AppService } from 'src/app/services/app.service';
import { StrongsModalComponent } from '../modal/strongs-modal.component';
@Component({ @Component({
selector: 'app-strongs-card', selector: 'app-strongs-card',
@ -18,6 +19,9 @@ export class StrongsCardComponent extends CardComponent {
asModal = false; asModal = false;
@ViewChild('strongs') strongsElement: ElementRef; @ViewChild('strongs') strongsElement: ElementRef;
@Input()
data: StrongsResult;
get strongsResult() { get strongsResult() {
return this.cardItem.data as StrongsResult; return this.cardItem.data as StrongsResult;
} }
@ -52,7 +56,7 @@ export class StrongsCardComponent extends CardComponent {
autoFocus: 'content', autoFocus: 'content',
}); });
} else { } else {
this.appService.getStrongs(sn, dict, this.cardItem); this.appService.getStrongs([sn], dict, this.cardItem);
} }
} }

View File

@ -1,22 +1,32 @@
<mat-toolbar> <mat-toolbar>
<mat-icon aria-hidden="false" aria-label="Strongs Entry Icon">{{ <mat-icon aria-hidden="false" aria-label="Strongs Entry Icon">{{
icon$ | async icon$ | async
}}</mat-icon> }}</mat-icon>
<div class="title">{{ cardItem.qry }}</div> <div class="title">{{ this.title }}</div>
<span class="close-button"> <span class="close-button">
<button <button
mat-icon-button mat-icon-button
mat-dialog-close mat-dialog-close
aria-label="Exit the Strongs Modal" aria-label="Exit the Strongs Modal"
> >
<mat-icon>cancel</mat-icon> <mat-icon>cancel</mat-icon>
</button> </button>
</span> </span>
</mat-toolbar> </mat-toolbar>
<mat-dialog-content class="content"> <mat-dialog-content class="content">
<br /> <mat-tab-group *ngIf="strongsResults.length > 1">
<app-strongs <mat-tab *ngFor="let card of strongsResults">
[data]="strongsResult" <ng-template mat-tab-label>{{ card.prefix }}{{ card.sn }}</ng-template>
(openPassage)="openPassage($event)" <app-strongs
></app-strongs> [data]="card"
(openPassage)="openPassage($event)"
></app-strongs>
</mat-tab>
</mat-tab-group>
<ng-container *ngIf="strongsResults.length === 1">
<app-strongs
[data]="strongsResults[0]"
(openPassage)="openPassage($event)"
></app-strongs>
</ng-container>
</mat-dialog-content> </mat-dialog-content>

View File

@ -1,5 +1,5 @@
import { Component, Inject, ChangeDetectionStrategy } from '@angular/core'; import { ChangeDetectionStrategy,Component, Inject } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { MAT_DIALOG_DATA,MatDialogRef } from '@angular/material/dialog';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { BibleReference } from 'src/app/common/bible-reference'; import { BibleReference } from 'src/app/common/bible-reference';
import { CardItem } from 'src/app/models/card-state'; import { CardItem } from 'src/app/models/card-state';
@ -15,15 +15,16 @@ import { AppService } from 'src/app/services/app.service';
}) })
export class StrongsModalComponent { export class StrongsModalComponent {
icon$: Observable<string>; icon$: Observable<string>;
strongsResult: StrongsResult; strongsResults: StrongsResult[];
title: string;
constructor( constructor(
@Inject(MAT_DIALOG_DATA) public cardItem: CardItem, @Inject(MAT_DIALOG_DATA) public cardItems: CardItem[],
public dialogRef: MatDialogRef<StrongsModalComponent>, public dialogRef: MatDialogRef<StrongsModalComponent>,
private appService: AppService private appService: AppService
) { ) {
console.log(cardItem); this.title = cardItems.map(o => o.qry).reduce((p, c) => `${p}, ${c}`);
this.strongsResult = cardItem.data as StrongsResult; this.strongsResults = cardItems.map(o => o.data as StrongsResult);
this.icon$ = appService.select((state) => state.settings.value.cardIcons.strongs); this.icon$ = appService.select((state) => state.settings.value.cardIcons.strongs);
} }
@ -33,6 +34,6 @@ export class StrongsModalComponent {
openPassage(p: string) { openPassage(p: string) {
const ref = BibleReference.makePassageFromReferenceKey(p); const ref = BibleReference.makePassageFromReferenceKey(p);
this.appService.getPassage(ref, this.cardItem); this.appService.getPassage(ref, this.cardItems[0]);
} }
} }

View File

@ -1,5 +1,6 @@
import { Component, Input, Output, EventEmitter } from '@angular/core'; import { Component, EventEmitter,Input, Output } from '@angular/core';
import { BibleReference } from 'src/app/common/bible-reference'; import { BibleReference } from 'src/app/common/bible-reference';
import { StrongsResult } from '../../models/strongs-state'; import { StrongsResult } from '../../models/strongs-state';
@Component({ @Component({

View File

@ -1,18 +1,12 @@
<div mat-dialog-title> <mat-toolbar>
<mat-toolbar> <mat-icon>bookmarks</mat-icon>
<mat-icon>bookmarks</mat-icon> <div class="title">Verse Picker</div>
<div class="title">Verse Picker</div> <span class="close-button">
<span class="close-button"> <button mat-icon-button mat-dialog-close aria-label="Exit the verse picker">
<button <mat-icon>cancel</mat-icon>
mat-icon-button </button>
mat-dialog-close </span>
aria-label="Exit the verse picker" </mat-toolbar>
>
<mat-icon>cancel</mat-icon>
</button>
</span>
</mat-toolbar>
</div>
<mat-dialog-content class="content"> <mat-dialog-content class="content">
<div> <div>
<span *ngIf="hasBook === false"> <span *ngIf="hasBook === false">

View File

@ -1,6 +1,6 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { MatDialogRef } from '@angular/material/dialog'; import { MatDialogRef } from '@angular/material/dialog';
import { Book, BibleReference } from 'src/app/common/bible-reference'; import { BibleReference,Book } from 'src/app/common/bible-reference';
import { AppService } from 'src/app/services/app.service'; import { AppService } from 'src/app/services/app.service';
@Component({ @Component({

View File

@ -1,6 +1,6 @@
import { Component, ElementRef, ViewChild, ChangeDetectionStrategy } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { Clipboard } from '@angular/cdk/clipboard'; import { Clipboard } from '@angular/cdk/clipboard';
import { ChangeDetectionStrategy,Component, ElementRef, ViewChild } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { BibleReference } from 'src/app/common/bible-reference'; import { BibleReference } from 'src/app/common/bible-reference';
import { CardComponent } from 'src/app/components/card.component'; import { CardComponent } from 'src/app/components/card.component';
import { WordLookupResult } from 'src/app/models/words-state'; import { WordLookupResult } from 'src/app/models/words-state';

View File

@ -1,9 +1,9 @@
import { IStorable } from '../common/storable';
import { NoteItem } from './note-state';
import { Overlap } from '../common/bible-reference'; import { Overlap } from '../common/bible-reference';
import { CardItem, CardIcons, DataReference } from './card-state';
import { SavedPage } from './page-state';
import { HashTable } from '../common/hashtable'; import { HashTable } from '../common/hashtable';
import { IStorable } from '../common/storable';
import { CardIcons, CardItem, DataReference } from './card-state';
import { NoteItem } from './note-state';
import { SavedPage } from './page-state';
export interface AppState { export interface AppState {
readonly currentSavedPage: SavedPage; readonly currentSavedPage: SavedPage;

View File

@ -1,7 +1,7 @@
import { NoteItem } from './note-state';
import { BiblePassageResult } from './passage-state'; import { BiblePassageResult } from './passage-state';
import { StrongsResult } from './strongs-state'; import { StrongsResult } from './strongs-state';
import { WordLookupResult } from './words-state'; import { WordLookupResult } from './words-state';
import { NoteItem } from './note-state';
export type CardData = BiblePassageResult | StrongsResult | WordLookupResult | NoteItem; export type CardData = BiblePassageResult | StrongsResult | WordLookupResult | NoteItem;

View File

@ -1,7 +1,6 @@
export type StrongsDictionary = 'heb' | 'grk'; export type StrongsDictionary = 'heb' | 'grk';
export interface StrongsResult { export interface StrongsResult {
readonly dict: StrongsDictionary;
readonly prefix: string; readonly prefix: string;
readonly sn: number; readonly sn: number;
readonly def: StrongsDefinition; readonly def: StrongsDefinition;

View File

@ -1,8 +1,8 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { CardItem, CardType } from 'src/app/models/card-state';
import { NoteEditModalComponent } from 'src/app/components/note/edit-modal/note-edit-modal.component';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { SubscriberBase } from 'src/app/common/subscriber-base'; import { SubscriberBase } from 'src/app/common/subscriber-base';
import { NoteEditModalComponent } from 'src/app/components/note/edit-modal/note-edit-modal.component';
import { CardItem, CardType } from 'src/app/models/card-state';
import { AppService } from 'src/app/services/app.service'; import { AppService } from 'src/app/services/app.service';
import { NavService } from 'src/app/services/nav.service'; import { NavService } from 'src/app/services/nav.service';

View File

@ -1,14 +1,15 @@
import { Component, OnInit, ViewChild, ChangeDetectionStrategy, ElementRef } from '@angular/core'; import { ChangeDetectionStrategy, Component, ElementRef,OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute, Router, NavigationEnd } from '@angular/router';
import { UntypedFormControl } from '@angular/forms'; import { UntypedFormControl } from '@angular/forms';
import { MatAutocomplete,MatAutocompleteTrigger } from '@angular/material/autocomplete';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { MatAutocompleteTrigger, MatAutocomplete } from '@angular/material/autocomplete'; import { ActivatedRoute, NavigationEnd,Router } from '@angular/router';
import { NavService } from '../../services/nav.service'; import { getFromCardCache } from 'src/app/common/card-cache-operations';
import { CardItem, CardType } from 'src/app/models/card-state';
import { AppService } from 'src/app/services/app.service';
import { SubscriberBase } from '../../common/subscriber-base'; import { SubscriberBase } from '../../common/subscriber-base';
import { VersePickerModalComponent } from '../../components/verse-picker-modal/verse-picker-modal.component'; import { VersePickerModalComponent } from '../../components/verse-picker-modal/verse-picker-modal.component';
import { CardItem, CardType } from 'src/app/models/card-state'; import { NavService } from '../../services/nav.service';
import { getFromCardCache } from 'src/app/common/card-cache-operations';
import { AppService } from 'src/app/services/app.service';
@Component({ @Component({
selector: 'app-search-page', selector: 'app-search-page',

View File

@ -1,14 +1,15 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { Overlap } from '../common/bible-reference'; import { Overlap } from '../common/bible-reference';
import { Storable, StorableType } from '../common/storable';
import { CardType, CardItem } from '../models/card-state';
import { SavedPage } from '../models/page-state';
import { AppState, User } from '../models/app-state';
import { getCardCacheKey } from '../common/card-cache-operations'; import { getCardCacheKey } from '../common/card-cache-operations';
import { MoveDirection } from '../common/move-direction'; import { MoveDirection } from '../common/move-direction';
import { Storable, StorableType } from '../common/storable';
import { AppState, User } from '../models/app-state';
import { CardItem,CardType } from '../models/card-state';
import { NoteItem } from '../models/note-state'; import { NoteItem } from '../models/note-state';
import { SavedPage } from '../models/page-state';
import { import {
addCardAction, addCardsAction,
addCardToSavedPageAction, addCardToSavedPageAction,
deleteNoteAction, deleteNoteAction,
findNotesAction, findNotesAction,
@ -273,7 +274,7 @@ describe('AppService Reducer', () => {
type: CardType.Strongs, type: CardType.Strongs,
}; };
const testState = addCardAction(card1, null).handle(preState); const testState = addCardsAction([card1], null).handle(preState);
expect(testState.currentCards.value[0]).toBe(card1, 'Failed to add first card to empty list'); expect(testState.currentCards.value[0]).toBe(card1, 'Failed to add first card to empty list');
const testState2 = updateCurrentPageAction().handle(testState); const testState2 = updateCurrentPageAction().handle(testState);
@ -289,7 +290,7 @@ describe('AppService Reducer', () => {
type: CardType.Passage, type: CardType.Passage,
}; };
const preState2 = addCardAction(card1, null).handle(preState); const preState2 = addCardsAction([card1], null).handle(preState);
const testState = savePageAction('my saved page').handle(preState2); const testState = savePageAction('my saved page').handle(preState2);
@ -328,7 +329,7 @@ describe('AppService Reducer', () => {
type: CardType.Strongs, type: CardType.Strongs,
}; };
const testState = addCardAction(card1, null).handle(preState); const testState = addCardsAction([card1], null).handle(preState);
expect(testState.currentCards.value[0]).toBe(card1, 'Failed to add first card to empty list'); expect(testState.currentCards.value[0]).toBe(card1, 'Failed to add first card to empty list');
const card2: CardItem = { const card2: CardItem = {
@ -337,7 +338,7 @@ describe('AppService Reducer', () => {
type: CardType.Strongs, type: CardType.Strongs,
}; };
const testState2 = addCardAction(card2, null).handle(testState); const testState2 = addCardsAction([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.length).toBe(2, 'Failed to add second card to list with 1 item');
expect(testState2.currentCards.value[1]).toBe(card2); expect(testState2.currentCards.value[1]).toBe(card2);
@ -370,7 +371,7 @@ describe('AppService Reducer', () => {
}, },
}).handle(testState2); }).handle(testState2);
const testState3 = addCardAction(card3, card2).handle(setState); const testState3 = addCardsAction([card3], card2).handle(setState);
expect(testState3.currentCards.value.length).toBe(3, 'Failed to add third card'); 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'); expect(testState3.currentCards.value[1]).toBe(card3, 'Failed to insert card above the second card');
@ -397,7 +398,7 @@ describe('AppService Reducer', () => {
}, },
}).handle(testState2); }).handle(testState2);
const testState4 = addCardAction(card3, card1).handle(setState); const testState4 = addCardsAction([card3], card1).handle(setState);
expect(testState4.currentCards.value.length).toBe(3, 'Failed to add third card'); 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'); expect(testState4.currentCards.value[1]).toBe(card3, 'Failed to insert card below the first card');
@ -424,7 +425,7 @@ describe('AppService Reducer', () => {
}, },
}).handle(testState2); }).handle(testState2);
const testState5 = addCardAction(card3, card1).handle(setState); const testState5 = addCardsAction([card3], card1).handle(setState);
expect(testState5.currentCards.value.length).toBe(3, 'Failed to add third card'); 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'); expect(testState5.currentCards.value[2]).toBe(card3, 'Failed to insert card at end of the list');
@ -451,7 +452,7 @@ describe('AppService Reducer', () => {
}, },
}).handle(testState2); }).handle(testState2);
const testState6 = addCardAction(card3, card1).handle(setState); const testState6 = addCardsAction([card3], card1).handle(setState);
expect(testState6.currentCards.value.length).toBe(3, 'Failed to add third card'); 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'); expect(testState6.currentCards.value[0]).toBe(card3, 'Failed to insert card at start of the list');
}); });
@ -463,7 +464,7 @@ describe('AppService Reducer', () => {
type: CardType.Strongs, type: CardType.Strongs,
}; };
const preState1 = addCardAction(oldCard, null).handle(preState); const preState1 = addCardsAction([oldCard], null).handle(preState);
const newCard: CardItem = { const newCard: CardItem = {
qry: 'H88', qry: 'H88',
@ -487,7 +488,7 @@ describe('AppService Reducer', () => {
type: CardType.Strongs, type: CardType.Strongs,
}; };
const preState1 = addCardAction(card, null).handle(preState); const preState1 = addCardsAction([card], null).handle(preState);
const testState = removeCardAction(card).handle(preState1); const testState = removeCardAction(card).handle(preState1);
@ -508,7 +509,7 @@ describe('AppService Reducer', () => {
type: CardType.Strongs, type: CardType.Strongs,
}; };
const preState1 = addCardAction(card1, null).handle(preState); const preState1 = addCardsAction([card1], null).handle(preState);
const card2: CardItem = { const card2: CardItem = {
qry: 'H88', qry: 'H88',
@ -516,7 +517,7 @@ describe('AppService Reducer', () => {
type: CardType.Strongs, type: CardType.Strongs,
}; };
const preState2 = addCardAction(card2, null).handle(preState1); const preState2 = addCardsAction([card2], null).handle(preState1);
expect(preState2.currentCards.value.length).toBe(2, 'Should have two cards'); expect(preState2.currentCards.value.length).toBe(2, 'Should have two cards');
expect(preState2.currentCards.value[0].qry).toBe('G123'); expect(preState2.currentCards.value[0].qry).toBe('G123');
expect(preState2.currentCards.value[1].qry).toBe('H88'); expect(preState2.currentCards.value[1].qry).toBe('H88');
@ -605,7 +606,4 @@ describe('AppService Reducer', () => {
//#endregion //#endregion
}); });
function newUserAction(preState: AppState, action1: any) {
throw new Error('Function not implemented.');
}

View File

@ -1,43 +1,37 @@
import { AngularFireDatabase } from '@angular/fire/compat/database';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { StorageMap } from '@ngx-pwa/local-storage';
import { UUID } from 'angular2-uuid'; import { UUID } from 'angular2-uuid';
import { lastValueFrom } from 'rxjs';
import { moveItem, moveItemUpOrDown } from '../common/array-operations';
import { BibleReference, Overlap,Section } from '../common/bible-reference';
import { getFromCardCache,removeFromCardCache, updateInCardCache } from '../common/card-cache-operations';
import { mergeCardList } from '../common/card-operations';
import { HashTable } from '../common/hashtable'; import { HashTable } from '../common/hashtable';
import { MoveDirection } from '../common/move-direction'; import { MoveDirection } from '../common/move-direction';
import { IStateAction } from '../common/state-service'; import { createReducingService,IReducingAction } from '../common/state-service';
import { IStorable, Storable, StorableType } from '../common/storable'; import { IStorable, Storable, StorableType } from '../common/storable';
import { mergeCardList } from '../common/card-operations'; import { AppState, DisplaySettings, Error,PageSettings, Settings, User } from '../models/app-state';
import { updateInCardCache, removeFromCardCache, getFromCardCache } from '../common/card-cache-operations'; import { CardItem, CardType, DataReference } from '../models/card-state';
import { moveItem, moveItemUpOrDown } from '../common/array-operations';
import { Section, BibleReference, Overlap } from '../common/bible-reference';
import { createStateService } from '../common/state-service';
import { SavedPage } from '../models/page-state';
import { CardType, CardItem, DataReference } from '../models/card-state';
import { AppState, User, Settings, DisplaySettings, PageSettings, Error } from '../models/app-state';
import { NoteItem } from '../models/note-state'; import { NoteItem } from '../models/note-state';
import { SavedPage } from '../models/page-state';
import { import {
Paragraph,
BiblePassage,
BibleVerse,
BibleParagraphPassage,
BibleParagraph, BibleParagraph,
BibleParagraphPassage,
BiblePassage,
BiblePassageResult, BiblePassageResult,
BibleVerse,
Paragraph,
} from '../models/passage-state'; } from '../models/passage-state';
import { import {
StrongsDefinition,
StrongsCrossReference,
RMACCrossReference, RMACCrossReference,
RMACDefinition, RMACDefinition,
StrongsCrossReference,
StrongsDefinition,
StrongsDictionary, StrongsDictionary,
StrongsResult, StrongsResult,
} from '../models/strongs-state'; } from '../models/strongs-state';
import { WordToStem, IndexResult, WordLookupResult } from '../models/words-state'; import { IndexResult, WordLookupResult,WordToStem } from '../models/words-state';
import { lastValueFrom } from 'rxjs';
const initialState: AppState = { const initialState: AppState = {
user: null, user: null,
@ -88,7 +82,7 @@ const initialState: AppState = {
}, },
}; };
interface AppAction extends IStateAction<AppState, AppActions> {} interface AppAction extends IReducingAction<AppState> {}
export function getNewestStorable<T>(candidate: IStorable<T>, incumbant: IStorable<T>): IStorable<T> { export function getNewestStorable<T>(candidate: IStorable<T>, incumbant: IStorable<T>): IStorable<T> {
// if the candidate is null, then return the state. // if the candidate is null, then return the state.
@ -105,19 +99,10 @@ export function getNewestStorable<T>(candidate: IStorable<T>, incumbant: IStorab
return incumbant; return incumbant;
} }
function appReducer(state: AppState, action: IStateAction<AppState, AppActions>): AppState {
if (state === undefined || action.handle === undefined) {
return initialState;
}
return action.handle(state);
}
//#region Saved Pages //#region Saved Pages
const savePageActionType = 'SAVE_PAGE';
export const savePageAction = (title: string): AppAction => { export const savePageAction = (title: string): AppAction => {
return { return {
type: savePageActionType,
handle(state: AppState) { handle(state: AppState) {
const savedPages = new Storable([ const savedPages = new Storable([
...(state.savedPages ? state.savedPages.value : []), ...(state.savedPages ? state.savedPages.value : []),
@ -134,10 +119,8 @@ export const savePageAction = (title: string): AppAction => {
}; };
}; };
const updateCurrentPageActionType = 'UPDATE_CURRENT_PAGE';
export const updateCurrentPageAction = (): AppAction => { export const updateCurrentPageAction = (): AppAction => {
return { return {
type: updateCurrentPageActionType,
handle(state: AppState) { handle(state: AppState) {
const current = { const current = {
id: state.currentSavedPage.id, id: state.currentSavedPage.id,
@ -161,10 +144,8 @@ export const updateCurrentPageAction = (): AppAction => {
}; };
}; };
const updateSavedPagesActionType = 'UPDATE_SAVED_PAGES';
export const updateSavedPagesAction = (oldSavedPages: IStorable<readonly SavedPage[]>): AppAction => { export const updateSavedPagesAction = (oldSavedPages: IStorable<readonly SavedPage[]>): AppAction => {
return { return {
type: updateSavedPagesActionType,
handle(state: AppState) { handle(state: AppState) {
const newSavedPages = getNewestStorable(oldSavedPages, state.savedPages); const newSavedPages = getNewestStorable(oldSavedPages, state.savedPages);
@ -192,10 +173,8 @@ export const updateSavedPagesAction = (oldSavedPages: IStorable<readonly SavedPa
}; };
}; };
const updateSavedPageActionType = 'UPDATE_SAVED_PAGES';
export const updateSavedPageAction = (savedPage: SavedPage): AppAction => { export const updateSavedPageAction = (savedPage: SavedPage): AppAction => {
return { return {
type: updateSavedPageActionType,
handle(state: AppState) { handle(state: AppState) {
const newSavedPages = new Storable<SavedPage[]>( const newSavedPages = new Storable<SavedPage[]>(
state.savedPages.value.map((o) => { state.savedPages.value.map((o) => {
@ -212,10 +191,8 @@ export const updateSavedPageAction = (savedPage: SavedPage): AppAction => {
}; };
}; };
const removeSavedPageActionType = 'REMOVE_SAVED_PAGE';
export const removeSavedPageAction = (savedPage: SavedPage): AppAction => { export const removeSavedPageAction = (savedPage: SavedPage): AppAction => {
return { return {
type: removeSavedPageActionType,
handle(state: AppState) { handle(state: AppState) {
const savedPages = new Storable<SavedPage[]>(state.savedPages.value.filter((o) => o.id !== savedPage.id)); const savedPages = new Storable<SavedPage[]>(state.savedPages.value.filter((o) => o.id !== savedPage.id));
const item = getNewestStorable(savedPages, state.savedPages); const item = getNewestStorable(savedPages, state.savedPages);
@ -229,10 +206,8 @@ export const removeSavedPageAction = (savedPage: SavedPage): AppAction => {
}; };
}; };
const moveSavedPageCardActionType = 'MOVE_SAVED_PAGE_CARD';
export const moveSavedPageCardAction = (oldSavedPage: SavedPage, fromIndex: number, toIndex: number): AppAction => { export const moveSavedPageCardAction = (oldSavedPage: SavedPage, fromIndex: number, toIndex: number): AppAction => {
return { return {
type: moveSavedPageCardActionType,
handle(state: AppState) { handle(state: AppState) {
const queries = moveItem(oldSavedPage.queries, fromIndex, toIndex); const queries = moveItem(oldSavedPage.queries, fromIndex, toIndex);
const savedPage = { const savedPage = {
@ -245,10 +220,8 @@ export const moveSavedPageCardAction = (oldSavedPage: SavedPage, fromIndex: numb
}; };
}; };
const addCardToSavedPageActionType = 'ADD_CARD_TO_SAVED_PAGE';
export const addCardToSavedPageAction = (card: CardItem, pageId: string): AppAction => { export const addCardToSavedPageAction = (card: CardItem, pageId: string): AppAction => {
return { return {
type: addCardToSavedPageActionType,
handle(state: AppState) { handle(state: AppState) {
const savedPages = new Storable([ const savedPages = new Storable([
...(state.savedPages ? state.savedPages.value : []).map((o) => { ...(state.savedPages ? state.savedPages.value : []).map((o) => {
@ -277,46 +250,45 @@ export const addCardToSavedPageAction = (card: CardItem, pageId: string): AppAct
//#region Cards //#region Cards
const addCardActionType = 'ADD_CARD'; export const addCardsAction = (cardsToAdd: CardItem[], nextToItem: CardItem): AppAction => {
export const addCardAction = (card: CardItem, nextToItem: CardItem): AppAction => {
return { return {
type: addCardActionType,
handle(state: AppState) { handle(state: AppState) {
let cards = []; let cards = [...state.currentCards.value];
let cache = state.cardCache;
if (nextToItem && state.settings.value.displaySettings.insertCardNextToItem) { for (let card of cardsToAdd) {
const idx = state.currentCards.value.indexOf(nextToItem); if (nextToItem && state.settings.value.displaySettings.insertCardNextToItem) {
const idx = cards.indexOf(nextToItem);
if (state.settings.value.displaySettings.appendCardToBottom) { if (state.settings.value.displaySettings.appendCardToBottom) {
const before = state.currentCards.value.slice(0, idx + 1); const before = cards.slice(0, idx + 1);
const after = state.currentCards.value.slice(idx + 1); const after = cards.slice(idx + 1);
cards = [...before, card, ...after]; cards = [...before, card, ...after];
} else {
const before = cards.slice(0, idx);
const after = cards.slice(idx);
cards = [...before, card, ...after];
}
} else { } else {
const before = state.currentCards.value.slice(0, idx); if (state.settings.value.displaySettings.appendCardToBottom) {
const after = state.currentCards.value.slice(idx); cards = [...cards, card];
cards = [...before, card, ...after]; } else {
} cards = [card, ...cards];
} else { }
if (state.settings.value.displaySettings.appendCardToBottom) {
cards = [...state.currentCards.value, card];
} else {
cards = [card, ...state.currentCards.value];
} }
cache = updateInCardCache(card, state.cardCache);
} }
return { return {
...state, ...state,
currentCards: new Storable(cards), currentCards: new Storable(cards),
cardCache: updateInCardCache(card, state.cardCache), cardCache: cache,
}; };
}, },
}; };
}; };
const updateCardActionType = 'UPDATE_CARD';
export const updateCardAction = (newCard: CardItem, oldCard: CardItem): AppAction => { export const updateCardAction = (newCard: CardItem, oldCard: CardItem): AppAction => {
return { return {
type: updateCardActionType,
handle(state: AppState) { handle(state: AppState) {
return { return {
...state, ...state,
@ -334,10 +306,8 @@ export const updateCardAction = (newCard: CardItem, oldCard: CardItem): AppActio
}; };
}; };
const removeCardActionType = 'REMOVE_CARD';
export const removeCardAction = (card: CardItem): AppAction => { export const removeCardAction = (card: CardItem): AppAction => {
return { return {
type: removeCardActionType,
handle(state: AppState) { handle(state: AppState) {
// potentially remove card from a saved page. // potentially remove card from a saved page.
const currentSavedPage = const currentSavedPage =
@ -371,10 +341,8 @@ export const removeCardAction = (card: CardItem): AppAction => {
}; };
}; };
const moveCardActionType = 'MOVE_CARD';
export const moveCardAction = (card: CardItem, direction: MoveDirection): AppAction => { export const moveCardAction = (card: CardItem, direction: MoveDirection): AppAction => {
return { return {
type: moveCardActionType,
handle(state: AppState) { handle(state: AppState) {
const cards = moveItemUpOrDown(state.currentCards.value, card, direction); const cards = moveItemUpOrDown(state.currentCards.value, card, direction);
@ -386,10 +354,8 @@ export const moveCardAction = (card: CardItem, direction: MoveDirection): AppAct
}; };
}; };
const updateCardsActionType = 'UPDATE_CARDS';
export const updateCardsAction = (cards: IStorable<CardItem[]>): AppAction => { export const updateCardsAction = (cards: IStorable<CardItem[]>): AppAction => {
return { return {
type: savePageActionType,
handle(state: AppState) { handle(state: AppState) {
let cardCache = { ...state.cardCache }; let cardCache = { ...state.cardCache };
for (const card of cards.value) { for (const card of cards.value) {
@ -404,10 +370,8 @@ export const updateCardsAction = (cards: IStorable<CardItem[]>): AppAction => {
}; };
}; };
const clearCardsActionType = 'CLEAR_CARDS';
export const clearCardsAction = (): AppAction => { export const clearCardsAction = (): AppAction => {
return { return {
type: clearCardsActionType,
handle(state: AppState) { handle(state: AppState) {
return { return {
...state, ...state,
@ -421,10 +385,8 @@ export const clearCardsAction = (): AppAction => {
//#region General //#region General
const updateErrorActionType = 'UPDATE_ERROR';
export const updateErrorAction = (error: Error): AppAction => { export const updateErrorAction = (error: Error): AppAction => {
return { return {
type: updateErrorActionType,
handle(state: AppState) { handle(state: AppState) {
return { return {
...state, ...state,
@ -434,10 +396,8 @@ export const updateErrorAction = (error: Error): AppAction => {
}; };
}; };
const setUserActionType = 'SET_USER';
export const setUserAction = (user: User): AppAction => { export const setUserAction = (user: User): AppAction => {
return { return {
type: setUserActionType,
handle(state: AppState) { handle(state: AppState) {
return { return {
...state, ...state,
@ -447,10 +407,8 @@ export const setUserAction = (user: User): AppAction => {
}; };
}; };
const updateAutoCompleteActionType = 'UPDATE_AUTOCOMPLETE';
export const updateAutoCompleteAction = (words: string[]): AppAction => { export const updateAutoCompleteAction = (words: string[]): AppAction => {
return { return {
type: updateAutoCompleteActionType,
handle(state: AppState) { handle(state: AppState) {
return { return {
...state, ...state,
@ -464,10 +422,8 @@ export const updateAutoCompleteAction = (words: string[]): AppAction => {
//#region Settings //#region Settings
const updateSettingsActionType = 'UPDATE_SETTINGS';
export const updateSettingsAction = (settings: IStorable<Settings>): AppAction => { export const updateSettingsAction = (settings: IStorable<Settings>): AppAction => {
return { return {
type: updateSettingsActionType,
handle(state: AppState) { handle(state: AppState) {
const item = getNewestStorable(settings, state.settings); const item = getNewestStorable(settings, state.settings);
@ -479,10 +435,8 @@ export const updateSettingsAction = (settings: IStorable<Settings>): AppAction =
}; };
}; };
const updateCardMergeStrategyActionType = 'UPDATE_CARD_MERGE_STRATEGY';
export const updateCardMergeStrategyAction = (mergeStrategy: Overlap): AppAction => { export const updateCardMergeStrategyAction = (mergeStrategy: Overlap): AppAction => {
return { return {
type: updateCardMergeStrategyActionType,
handle(state: AppState) { handle(state: AppState) {
const settings = new Storable<Settings>({ const settings = new Storable<Settings>({
...state.settings.value, ...state.settings.value,
@ -496,10 +450,8 @@ export const updateCardMergeStrategyAction = (mergeStrategy: Overlap): AppAction
}; };
}; };
const updateCardFontSizeActionType = 'UPDATE_CARD_FONT_SIZE';
export const updateCardFontSizeAction = (cardFontSize: number): AppAction => { export const updateCardFontSizeAction = (cardFontSize: number): AppAction => {
return { return {
type: updateCardFontSizeActionType,
handle(state: AppState) { handle(state: AppState) {
const settings = new Storable<Settings>({ const settings = new Storable<Settings>({
...state.settings.value, ...state.settings.value,
@ -514,10 +466,8 @@ export const updateCardFontSizeAction = (cardFontSize: number): AppAction => {
}; };
}; };
const updateCardFontFamilyActionType = 'UPDATE_CARD_FONT_FAMILY';
export const updateCardFontFamilyAction = (cardFontFamily: string): AppAction => { export const updateCardFontFamilyAction = (cardFontFamily: string): AppAction => {
return { return {
type: updateCardFontFamilyActionType,
handle(state: AppState) { handle(state: AppState) {
const settings = new Storable<Settings>({ const settings = new Storable<Settings>({
...state.settings.value, ...state.settings.value,
@ -536,10 +486,8 @@ export const updateCardFontFamilyAction = (cardFontFamily: string): AppAction =>
//#region Notes //#region Notes
const findNotesActionType = 'FIND_NOTES';
export const findNotesAction = (qry: string, nextToItem: CardItem): AppAction => { export const findNotesAction = (qry: string, nextToItem: CardItem): AppAction => {
return { return {
type: findNotesActionType,
handle(state: AppState) { handle(state: AppState) {
const notes = state.notes.value const notes = state.notes.value
.filter((o) => o.title.search(qry) > -1) .filter((o) => o.title.search(qry) > -1)
@ -587,10 +535,8 @@ export const findNotesAction = (qry: string, nextToItem: CardItem): AppAction =>
}; };
}; };
const getNotesActionType = 'GET_NOTE';
export const getNotesAction = (noteId: string, nextToItem: CardItem): AppAction => { export const getNotesAction = (noteId: string, nextToItem: CardItem): AppAction => {
return { return {
type: getNotesActionType,
handle(state: AppState) { handle(state: AppState) {
const note = state.notes.value.find((o) => o.id === noteId); const note = state.notes.value.find((o) => o.id === noteId);
const card: CardItem = { const card: CardItem = {
@ -599,15 +545,13 @@ export const getNotesAction = (noteId: string, nextToItem: CardItem): AppAction
data: note, data: note,
}; };
return addCardAction(card, nextToItem).handle(state); return addCardsAction([card], nextToItem).handle(state);
}, },
}; };
}; };
const updateNotesActionType = 'UPDATE_NOTES';
export const updateNotesAction = (notes: IStorable<readonly NoteItem[]>): AppAction => { export const updateNotesAction = (notes: IStorable<readonly NoteItem[]>): AppAction => {
return { return {
type: updateNotesActionType,
handle(state: AppState) { handle(state: AppState) {
return { return {
...state, ...state,
@ -617,10 +561,8 @@ export const updateNotesAction = (notes: IStorable<readonly NoteItem[]>): AppAct
}; };
}; };
const saveNoteActionType = 'SAVE_NOTE';
export const saveNoteAction = (note: NoteItem): AppAction => { export const saveNoteAction = (note: NoteItem): AppAction => {
return { return {
type: saveNoteActionType,
handle(state: AppState) { handle(state: AppState) {
// you may be creating a new note or updating an existing. // you may be creating a new note or updating an existing.
// if its an update, you need to update the note in the following: // if its an update, you need to update the note in the following:
@ -642,10 +584,8 @@ export const saveNoteAction = (note: NoteItem): AppAction => {
}; };
}; };
const deleteNoteActionType = 'DELETE_NOTE';
export const deleteNoteAction = (note: NoteItem): AppAction => { export const deleteNoteAction = (note: NoteItem): AppAction => {
return { return {
type: deleteNoteActionType,
handle(state: AppState) { handle(state: AppState) {
// the note may be in any of the following: // the note may be in any of the following:
// * card list could have it. // * card list could have it.
@ -692,42 +632,10 @@ export const deleteNoteAction = (note: NoteItem): AppAction => {
//#endregion //#endregion
type AppActions =
// Saved Page Actions
| typeof savePageActionType
| typeof updateCurrentPageActionType
| typeof updateSavedPagesActionType
| typeof updateSavedPageActionType
| typeof removeSavedPageActionType
| typeof moveSavedPageCardActionType
| typeof addCardToSavedPageActionType
// Card Actions
| typeof addCardActionType
| typeof updateCardActionType
| typeof removeCardActionType
| typeof moveCardActionType
| typeof updateCardsActionType
| typeof clearCardsActionType
// General Actions
| typeof updateErrorActionType
| typeof setUserActionType
| typeof updateAutoCompleteActionType
// Settings Actions
| typeof updateSettingsActionType
| typeof updateCardMergeStrategyActionType
| typeof updateCardFontSizeActionType
| typeof updateCardFontFamilyActionType
// Note Actions
| typeof findNotesActionType
| typeof getNotesActionType
| typeof updateNotesActionType
| typeof saveNoteActionType
| typeof deleteNoteActionType;
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class AppService extends createStateService(appReducer, initialState) { export class AppService extends createReducingService(initialState) {
private wordToStem: Map<string, string>; private wordToStem: Map<string, string>;
private paragraphs: HashTable<Paragraph>; private paragraphs: HashTable<Paragraph>;
private searchIndexArray: string[]; private searchIndexArray: string[];
@ -801,7 +709,7 @@ export class AppService extends createStateService(appReducer, initialState) {
]); ]);
private readonly dataPath = 'assets/data'; private readonly dataPath = 'assets/data';
constructor(private http: HttpClient, private localStorageService: StorageMap, private db: AngularFireDatabase) { constructor(private http: HttpClient) {
super(); super();
this.searchIndexArray = this.buildIndexArray().sort(); this.searchIndexArray = this.buildIndexArray().sort();
@ -837,7 +745,7 @@ export class AppService extends createStateService(appReducer, initialState) {
qry = qry.split('"').join(''); // remove any quotes, just in case qry = qry.split('"').join(''); // remove any quotes, just in case
if (qry.search(/[0-9]/i) === -1) { if (qry.search(/\d/i) === -1) {
// its a word // its a word
for (const item of BibleReference.Books) { for (const item of BibleReference.Books) {
if ( if (
@ -862,7 +770,7 @@ export class AppService extends createStateService(appReducer, initialState) {
} }
this.dispatch(updateAutoCompleteAction(words)); this.dispatch(updateAutoCompleteAction(words));
} else if (qry.search(/(H|G)[0-9]/i) !== -1) { } else if (qry.search(/(h|g)\d/i) !== -1) {
// its a strongs lookup // its a strongs lookup
if (qry.substring(0, 1).toUpperCase() === 'H') { if (qry.substring(0, 1).toUpperCase() === 'H') {
const num = parseInt(qry.substring(1), 10); const num = parseInt(qry.substring(1), 10);
@ -897,7 +805,7 @@ export class AppService extends createStateService(appReducer, initialState) {
if (!card) { if (!card) {
return; return;
} }
this.dispatch(addCardAction(card, nextToItem)); this.dispatch(addCardsAction([card], nextToItem));
} }
async updateCards(queries: IStorable<readonly DataReference[]>) { async updateCards(queries: IStorable<readonly DataReference[]>) {
@ -942,7 +850,7 @@ export class AppService extends createStateService(appReducer, initialState) {
type: CardType.Note, type: CardType.Note,
data, data,
} as CardItem; } as CardItem;
} else if (qry.search(/[0-9]/i) === -1) { } else if (qry.search(/\d/i) === -1) {
// // its a search term. // // its a search term.
const data = await this.getWordsFromApi(qry); const data = await this.getWordsFromApi(qry);
if (!data) { if (!data) {
@ -953,7 +861,7 @@ export class AppService extends createStateService(appReducer, initialState) {
type: CardType.Word, type: CardType.Word,
data, data,
} as CardItem; } as CardItem;
} else if (qry.search(/(H|G)[0-9]/i) !== -1) { } else if (qry.search(/(h|g)\d/i) !== -1) {
// its a strongs lookup // its a strongs lookup
const dict = qry.substring(0, 1).search(/h/i) !== -1 ? 'heb' : 'grk'; const dict = qry.substring(0, 1).search(/h/i) !== -1 ? 'heb' : 'grk';
const strongsNumber = qry.substring(1, qry.length); const strongsNumber = qry.substring(1, qry.length);
@ -1092,13 +1000,17 @@ export class AppService extends createStateService(appReducer, initialState) {
//#region Strongs //#region Strongs
async getStrongs(strongsNumber: string, dict: StrongsDictionary, nextToItem: CardItem = null) { async getStrongs(strongsNumber: string[], dict: StrongsDictionary, nextToItem: CardItem = null) {
const card = await this.getStrongsCard(strongsNumber, dict); const cards = [];
if (!card) { for (const sn of strongsNumber) {
return; // nothing was returned. so an error occurred. const card = await this.getStrongsCard(sn, dict);
cards.push(card);
}
if (cards.length < 1) {
return;
} }
this.dispatch(addCardAction(card, nextToItem)); this.dispatch(addCardsAction(cards, nextToItem));
} }
async getStrongsCard(strongsNumber: string, dict: StrongsDictionary) { async getStrongsCard(strongsNumber: string, dict: StrongsDictionary) {
@ -1174,7 +1086,7 @@ export class AppService extends createStateService(appReducer, initialState) {
const rmacCrossReferences = await lastValueFrom(this.http.get<RMACCrossReference[]>(url)); const rmacCrossReferences = await lastValueFrom(this.http.get<RMACCrossReference[]>(url));
// deal with RMAC // deal with RMAC
const referencesForThisStrongsNumber = rmacCrossReferences.filter((el, i) => { const referencesForThisStrongsNumber = rmacCrossReferences.filter((el) => {
return el.i === sn.toString(); return el.i === sn.toString();
}); });
@ -1213,7 +1125,7 @@ export class AppService extends createStateService(appReducer, initialState) {
return; // nothing was returned. so an error occurred. return; // nothing was returned. so an error occurred.
} }
this.dispatch(addCardAction(card, nextToItem)); this.dispatch(addCardsAction([card], nextToItem));
} }
async updatePassage(oldCard: CardItem, ref: BibleReference) { async updatePassage(oldCard: CardItem, ref: BibleReference) {
@ -1354,7 +1266,6 @@ export class AppService extends createStateService(appReducer, initialState) {
// create an initial paragraph to hold verses that might come before a paragraph. // create an initial paragraph to hold verses that might come before a paragraph.
let para = { p: { h: '', p: 0 }, vss: [] }; let para = { p: { h: '', p: 0 }, vss: [] };
const paras = []; const paras = [];
const vss: BibleVerse[] = [];
// for each verse in the chapter, break them into paragraphs. // for each verse in the chapter, break them into paragraphs.
for (const v of ch.vss) { for (const v of ch.vss) {
@ -1402,7 +1313,7 @@ export class AppService extends createStateService(appReducer, initialState) {
data: result, data: result,
} as CardItem; } as CardItem;
this.dispatch(addCardAction(card, nextToItem)); this.dispatch(addCardsAction([card], nextToItem));
} }
private async getWordsFromApi(qry: string): Promise<WordLookupResult> { private async getWordsFromApi(qry: string): Promise<WordLookupResult> {
@ -1463,7 +1374,7 @@ export class AppService extends createStateService(appReducer, initialState) {
if (excluded.length > 0) { if (excluded.length > 0) {
this.dispatch( this.dispatch(
updateErrorAction({ updateErrorAction({
msg: `The ${excluded.length > 1 ? 'words' : 'word'} "${excluded.reduce((prev, curr, idx, arr) => { msg: `The ${excluded.length > 1 ? 'words' : 'word'} "${excluded.reduce((prev, curr) => {
return `${prev}, ${curr}`; return `${prev}, ${curr}`;
})}" ${excluded.length > 1 ? 'have' : 'has'} been excluded from search because it is too common.`, })}" ${excluded.length > 1 ? 'have' : 'has'} been excluded from search because it is too common.`,
}) })

View File

@ -2,13 +2,14 @@ import { Injectable } from '@angular/core';
import { AngularFireDatabase, AngularFireObject } from '@angular/fire/compat/database'; import { AngularFireDatabase, AngularFireObject } from '@angular/fire/compat/database';
import { DataSnapshot } from '@angular/fire/compat/database/interfaces'; import { DataSnapshot } from '@angular/fire/compat/database/interfaces';
import { UUID } from 'angular2-uuid'; import { UUID } from 'angular2-uuid';
import { Overlap } from '../common/bible-reference'; import { Overlap } from '../common/bible-reference';
import { Settings, User } from '../models/app-state';
import { SavedPage } from '../models/page-state';
import { CardType, DataReference } from '../models/card-state';
import { StorageService } from './storage.service';
import { Storable, StorableType } from '../common/storable'; import { Storable, StorableType } from '../common/storable';
import { Settings, User } from '../models/app-state';
import { CardType, DataReference } from '../models/card-state';
import { SavedPage } from '../models/page-state';
import { AppService } from './app.service'; import { AppService } from './app.service';
import { StorageService } from './storage.service';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',

View File

@ -1,20 +1,19 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { StorageMap } from '@ngx-pwa/local-storage';
import { AngularFireDatabase, AngularFireObject } from '@angular/fire/compat/database'; import { AngularFireDatabase, AngularFireObject } from '@angular/fire/compat/database';
import { DataSnapshot } from '@angular/fire/compat/database/interfaces'; import { DataSnapshot } from '@angular/fire/compat/database/interfaces';
import { StorageMap } from '@ngx-pwa/local-storage';
import { createSelector } from 'reselect';
import { lastValueFrom } from 'rxjs';
import { SubscriberBase } from '../common/subscriber-base'; import { isNullOrUndefined } from '../common/helpers';
import { IStorable, StorableType, UserVersion } from '../common/storable'; import { IStorable, StorableType, UserVersion } from '../common/storable';
import { MigrationVersion0to1 } from './migration0to1.service'; import { SubscriberBase } from '../common/subscriber-base';
import { AppState,Settings, User } from '../models/app-state';
import { User, Settings, AppState } from '../models/app-state'; import { DataReference } from '../models/card-state';
import { NoteItem } from '../models/note-state'; import { NoteItem } from '../models/note-state';
import { SavedPage } from '../models/page-state'; import { SavedPage } from '../models/page-state';
import { isNullOrUndefined } from '../common/helpers';
import { DataReference } from '../models/card-state';
import { createSelector } from 'reselect';
import { AppService } from './app.service'; import { AppService } from './app.service';
import { lastValueFrom } from 'rxjs'; import { MigrationVersion0to1 } from './migration0to1.service';
/** /**
* This class handles all the storage needs of the application. It handles both * This class handles all the storage needs of the application. It handles both

View File

@ -1,4 +1,5 @@
import 'zone.js/testing'; import 'zone.js/testing';
import { getTestBed } from '@angular/core/testing'; import { getTestBed } from '@angular/core/testing';
import { import {
BrowserDynamicTestingModule, BrowserDynamicTestingModule,