fix loading saved pages and normal pages,

add settings to savePage, createNote
fixed the router,
cleaned up the note dialog so it can handle new/existing,
added page edit modal
This commit is contained in:
Jason Wall 2020-08-02 19:14:59 -04:00
parent f305e106af
commit 7f5783bf82
13 changed files with 283 additions and 57 deletions

View File

@ -5,25 +5,28 @@ import { SearchPage } from './search/components/search-page/search.page';
const routes: Routes = [
{
path: '',
redirectTo: 'search/',
pathMatch: 'full',
redirectTo: 'search',
},
{
path: 'search/',
path: 'search',
pathMatch: 'prefix',
component: SearchPage,
},
{
path: 'search/:term',
pathMatch: 'prefix',
component: SearchPage,
},
{
path: 'saved/:id',
pathMatch: 'prefix',
component: SearchPage,
},
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
imports: [RouterModule.forRoot(routes, { onSameUrlNavigation: 'reload' })],
exports: [RouterModule],
})
export class AppRoutingModule {}

View File

@ -13,7 +13,7 @@
<a
*ngFor="let p of mainPages$ | async"
mat-list-item
[routerLink]="['/', p.route]"
[routerLink]="[p.route]"
><mat-icon color="accenovert">{{ p.icon }}</mat-icon> {{ p.title }}</a
>
</mat-nav-list>
@ -23,7 +23,7 @@
<a
*ngFor="let p of savedPages$ | async"
mat-list-item
[routerLink]="['/', 'saved', p.id]"
[routerLink]="['saved', p.id]"
><mat-icon color="accenovert">playlist_play</mat-icon>
{{ p.title }}</a
>
@ -38,6 +38,22 @@
position="end"
[opened]="false"
>
<mat-toolbar>Search Settings</mat-toolbar>
<mat-nav-list>
<mat-list-item>
<a mat-list-item (click)="savePage()"
><mat-icon color="accenovert">save</mat-icon> Save Results as New
Page</a
>
</mat-list-item>
<mat-list-item>
<a mat-list-item (click)="createNote()"
><mat-icon color="accenovert">create</mat-icon> Create a New Note</a
>
</mat-list-item>
</mat-nav-list>
<app-settings></app-settings>
</mat-sidenav>
<mat-sidenav-content>

View File

@ -5,13 +5,19 @@ import { Observable } from 'rxjs';
import { Breakpoints, BreakpointObserver } from '@angular/cdk/layout';
import { map, shareReplay } from 'rxjs/operators';
import { MatSidenav } from '@angular/material/sidenav';
import { MatDialog } from '@angular/material/dialog';
import { PageEditModalComponent } from './search/components/page-edit-modal/page-edit-modal.component';
import { NoteEditModalComponent } from './search/components/note-edit-modal/note-edit-modal.component';
import { SavedPage, Page } from './models/app-state';
import { Router } from '@angular/router';
import { SubscriberComponent } from './common/components/subscriber.component';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent implements AfterViewInit {
export class AppComponent extends SubscriberComponent implements AfterViewInit {
savedPages$ = this.appService.select((state) => state.savedPages);
mainPages$ = this.appService.select((state) => state.mainPages);
fontSize$ = this.appService.select(
@ -34,8 +40,11 @@ export class AppComponent implements AfterViewInit {
constructor(
private appService: AppService,
private navService: NavService,
private breakpointObserver: BreakpointObserver
private breakpointObserver: BreakpointObserver,
private dialog: MatDialog
) {
super();
this.appService.initSavedPages();
this.appService.initDisplaySettings();
@ -50,4 +59,12 @@ export class AppComponent implements AfterViewInit {
ngAfterViewInit(): void {
this.navService.setSidenav(this.sidenav, this.settings);
}
savePage() {
this.dialog.open(PageEditModalComponent);
}
createNote() {
this.dialog.open(NoteEditModalComponent);
}
}

View File

@ -16,6 +16,7 @@ import { WordsComponent } from './search/components/words/words.component';
import { NoteComponent } from './search/components/note/note.component';
import { SettingsComponent } from './common/components/settings/settings.component';
import { PageEditModalComponent } from './search/components/page-edit-modal/page-edit-modal.component';
import { NoteEditModalComponent } from './search/components/note-edit-modal/note-edit-modal.component';
import { VersePickerModalComponent } from './search/components/verse-picker/verse-picker-modal.component';
@ -64,6 +65,7 @@ import { ClipboardModule } from '@angular/cdk/clipboard';
StrongsComponent,
WordsComponent,
NoteComponent,
PageEditModalComponent,
NoteEditModalComponent,
VersePickerModalComponent,
SettingsComponent,
@ -78,6 +80,7 @@ import { ClipboardModule } from '@angular/cdk/clipboard';
BrowserAnimationsModule,
NgxMdModule.forRoot(),
MatSidenavModule,
MatToolbarModule,
MatIconModule,

View File

@ -1,5 +1,7 @@
export interface AppState {
readonly currentPage: SavedPage;
readonly savedPages: readonly SavedPage[];
readonly savedPagesLoaded: boolean;
readonly mainPages: readonly Page[];
readonly cards: readonly CardItem[];
readonly autocomplete: readonly string[];

View File

@ -2,15 +2,6 @@
<mat-toolbar>
<mat-icon>edit</mat-icon>
<div class="title">Edit: {{ data.title }}</div>
<span class="close-button">
<button
mat-icon-button
mat-dialog-close
aria-label="Exit the verse picker"
>
<mat-icon>cancel</mat-icon>
</button>
</span>
</mat-toolbar>
</div>
<mat-dialog-content>

View File

@ -3,6 +3,7 @@ import { FormGroup, FormBuilder } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { CardItem, NoteItem } from '../../../models/app-state';
import { AppService } from '../../../services/app.service';
import { UUID } from 'angular2-uuid';
@Component({
selector: 'app-note-edit-modal',
@ -12,6 +13,7 @@ import { AppService } from '../../../services/app.service';
export class NoteEditModalComponent {
noteForm: FormGroup;
data: NoteItem;
isNew = false;
constructor(
@Inject(MAT_DIALOG_DATA) public cardItem: CardItem,
@ -19,7 +21,17 @@ export class NoteEditModalComponent {
private appService: AppService,
private fb: FormBuilder
) {
this.data = cardItem.data as NoteItem;
if (cardItem) {
this.data = cardItem.data as NoteItem;
} else {
this.isNew = true;
this.data = {
id: UUID.UUID(),
title: 'Title Here',
content: '# Content Here\nIn Markdown format.',
xref: '',
};
}
this.noteForm = this.fb.group(this.data);
}
@ -28,17 +40,31 @@ export class NoteEditModalComponent {
}
save() {
this.appService.editNote(
{
...this.cardItem,
if (this.isNew) {
this.appService.createNote({
qry: '',
dict: 'n/a',
type: 'Note',
data: {
...this.cardItem.data,
...this.data,
title: this.noteForm.get('title').value,
content: this.noteForm.get('content').value,
},
},
this.cardItem
);
});
} else {
this.appService.editNote(
{
...this.cardItem,
data: {
...this.cardItem.data,
title: this.noteForm.get('title').value,
content: this.noteForm.get('content').value,
},
},
this.cardItem
);
}
this.dialogRef.close();
}
}

View File

@ -0,0 +1,18 @@
<div mat-dialog-title>
<mat-toolbar>
<mat-icon>save</mat-icon>
<div class="title">{{ dialogTitle }}</div>
</mat-toolbar>
</div>
<mat-dialog-content>
<form [formGroup]="form">
<mat-form-field class="page-title">
<mat-label>Title</mat-label>
<input formControlName="title" matInput />
</mat-form-field>
</form>
</mat-dialog-content>
<div mat-dialog-actions>
<button mat-button (click)="save()">Save</button>
<button mat-button (click)="cancel()">Cancel</button>
</div>

View File

@ -0,0 +1,20 @@
.close-button {
float: right;
mat-icon {
font-size: 2rem;
}
}
.title {
width: 100%;
padding-left: 1rem;
font-size: 1.5rem;
}
.mat-form-field {
display: block;
}
.page-title {
min-width: 50vw;
}

View File

@ -0,0 +1,38 @@
import { Component, Inject } from '@angular/core';
import { FormGroup, FormBuilder } from '@angular/forms';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { AppService } from '../../../services/app.service';
@Component({
selector: 'app-page-edit-modal',
templateUrl: 'page-edit-modal.component.html',
styleUrls: ['./page-edit-modal.component.scss'],
})
export class PageEditModalComponent {
form: FormGroup;
dialogTitle = 'Save Page using Current Cards';
constructor(
@Inject(MAT_DIALOG_DATA) public title: string,
public dialogRef: MatDialogRef<PageEditModalComponent>,
private appService: AppService,
private fb: FormBuilder
) {
if (title) {
this.dialogTitle = 'Edit Page Name';
}
this.form = this.fb.group({
title,
});
}
cancel() {
this.dialogRef.close();
}
save() {
this.appService.savePage(this.form.get('title').value);
this.dialogRef.close();
}
}

View File

@ -1,5 +1,5 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ActivatedRoute, Router, NavigationEnd } from '@angular/router';
import { FormControl } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { AppService } from '../../../services/app.service';
@ -22,6 +22,9 @@ import {
export class SearchPage extends SubscriberComponent implements OnInit {
cards$ = this.appService.select((state) => state.cards);
suggestions$ = this.appService.select((state) => state.autocomplete);
savedPagedLoaded = false;
searchControl = new FormControl();
@ViewChild(MatAutocomplete)
@ -32,25 +35,31 @@ export class SearchPage extends SubscriberComponent implements OnInit {
constructor(
private activatedRoute: ActivatedRoute,
private appService: AppService,
private router: Router,
public navService: NavService,
public dialog: MatDialog
) {
super();
// you need to know when certain things are loaded, so you can then do stuff.
// this will watch the state and store those items in your class so you can monitor them.
this.addSubscription(
this.appService.state$.subscribe((state) => {
this.savedPagedLoaded = state.savedPagesLoaded;
})
);
}
ngOnInit() {
// if a route was passed in, perform a search.
if (this.activatedRoute.snapshot.paramMap.has('term')) {
const term = this.activatedRoute.snapshot.paramMap.get('term');
this.search(term);
}
// if a route was passed in, perform a search.
if (this.activatedRoute.snapshot.paramMap.has('id')) {
const id = this.activatedRoute.snapshot.paramMap.get('id');
this.appService.getSavedPage(id);
}
this.addSubscription(
this.router.events.subscribe((evt) => {
if (evt instanceof NavigationEnd) {
this.init();
}
console.log(evt);
})
);
this.init();
// subscribe to autocomplete input control's changes
this.addSubscription(
this.searchControl.valueChanges.subscribe((value: string) =>
@ -59,6 +68,32 @@ export class SearchPage extends SubscriberComponent implements OnInit {
);
}
init() {
if (this.activatedRoute.snapshot.paramMap.has('term')) {
// if a route was passed in, perform a search.
const term = this.activatedRoute.snapshot.paramMap.get('term');
this.search(term);
} else if (this.activatedRoute.snapshot.paramMap.has('id')) {
// if this is a saved page
const id = this.activatedRoute.snapshot.paramMap.get('id');
this.onSavedPagedLoaded(id);
this.navService.close(); // close the nav immediately.
} else {
// its a blank search. load nothing.
}
}
// monitor the saved paged loaded property, and fire the action once it turns true, then stop.
onSavedPagedLoaded(id: string) {
if (this.savedPagedLoaded) {
this.appService.getSavedPage(id);
return;
}
setTimeout(() => {
this.onSavedPagedLoaded(id);
}, 100);
}
launchPicker() {
this.dialog.open(VersePickerModalComponent);
}

View File

@ -44,7 +44,9 @@ const initialState: AppState = {
},
],
autocomplete: [],
currentPage: null,
savedPages: [],
savedPagesLoaded: false,
mainPages: [
{ title: PageTitles.Search, icon: PageIcons.Search, route: 'search' },
{ title: PageTitles.Settings, icon: PageIcons.Settings, route: 'settings' },
@ -71,14 +73,14 @@ const initialState: AppState = {
};
type AppAction =
| {
type: 'GET_SAVED_PAGES';
pages: SavedPage[];
}
| {
type: 'GET_SAVED_PAGE';
pageid: string;
}
| {
type: 'UPDATE_SAVED_PAGES';
savedPages: SavedPage[];
}
| {
type: 'ADD_CARD';
card: CardItem;
@ -133,6 +135,13 @@ function reducer(state: AppState, action: AppAction): AppState {
autocomplete: [...action.words],
};
}
case 'UPDATE_SAVED_PAGES': {
return {
...state,
savedPagesLoaded: true,
savedPages: action.savedPages,
};
}
case 'GET_SAVED_PAGE': {
const page = state.savedPages.find(
(o) => o.id.toString() === action.pageid
@ -144,15 +153,10 @@ function reducer(state: AppState, action: AppAction): AppState {
return {
...state,
currentPage: page,
cards: [...page.queries],
};
}
case 'GET_SAVED_PAGES': {
return {
...state,
savedPages: [...action.pages],
};
}
case 'ADD_CARD': {
let cards = [];
@ -261,21 +265,17 @@ export class AppService extends createStateService(reducer, initialState) {
const exists = await this.localStorageService.has('savedPages').toPromise();
if (exists) {
const pages = await this.getSavedPagesApi();
const savedPages = (await this.localStorageService
.get('savedPages')
.toPromise()) as SavedPage[];
this.dispatch({
type: 'GET_SAVED_PAGES',
pages,
type: 'UPDATE_SAVED_PAGES',
savedPages,
});
}
}
private async getSavedPagesApi() {
return (await this.localStorageService
.get('savedPages')
.toPromise()) as SavedPage[];
}
getSavedPage(pageid: string) {
this.dispatch({
type: 'GET_SAVED_PAGE',
@ -283,6 +283,40 @@ export class AppService extends createStateService(reducer, initialState) {
});
}
savePage(title: string) {
const state = this.getState();
const savedPages = [
...state.savedPages,
{
// create a new saved page object
title,
id: UUID.UUID().toString(),
queries: [...state.cards],
},
];
this.localStorageService.set('savedPages', savedPages).subscribe(
// success
() => {
this.dispatch({
type: 'UPDATE_SAVED_PAGES',
savedPages,
});
},
// error
() => {
this.dispatch({
type: 'UPDATE_ERROR',
error: {
// tslint:disable-next-line: quotemark
msg: "Something went wrong and the page wasn't saved. :(",
},
});
}
);
}
//#endregion
//#region Display Settings
@ -357,6 +391,28 @@ export class AppService extends createStateService(reducer, initialState) {
});
}
async createNote(card: CardItem, nextToItem: CardItem = null) {
this.saveNoteApi(card.data as NoteItem).subscribe(
// success
() => {
this.dispatch({
type: 'ADD_CARD',
card,
nextToItem,
});
},
// error
() => {
this.dispatch({
type: 'UPDATE_ERROR',
error: {
// tslint:disable-next-line: quotemark
msg: "Something went wrong and the note wasn't saved. :(",
},
});
}
);
}
async editNote(newCard: CardItem, oldCard: CardItem) {
this.saveNoteApi(newCard.data as NoteItem).subscribe(
// success

View File

@ -17,8 +17,9 @@ export class NavService {
return this.sidenav.open();
}
public close() {
return this.sidenav.close();
public async close() {
const r = await this.sidenav.close();
return r;
}
public toggle(): void {