basic page admin

bug fix in handling of storage
This commit is contained in:
Jason Wall 2020-08-09 18:53:04 -04:00
parent 44072579a1
commit 27569e4c50
20 changed files with 293 additions and 18 deletions

View File

@ -1,6 +1,7 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { SearchPage } from './pages/search/search.page';
import { SavedPagesAdminPage } from './pages/saved-pages-admin/saved-pages-admin.page';
const routes: Routes = [
{
@ -19,10 +20,15 @@ const routes: Routes = [
component: SearchPage,
},
{
path: 'saved/:id',
path: 'page/:id',
pathMatch: 'prefix',
component: SearchPage,
},
{
path: 'saved/admin',
pathMatch: 'prefix',
component: SavedPagesAdminPage,
},
];
@NgModule({

View File

@ -23,7 +23,7 @@
<a
*ngFor="let p of savedPages$ | async"
mat-list-item
[routerLink]="['saved', p.id]"
[routerLink]="['page', p.id]"
><mat-icon color="accenovert">library_books</mat-icon>
{{ p.title }}</a
>

View File

@ -15,7 +15,7 @@ import { MatSnackBar } from '@angular/material/snack-bar';
styleUrls: ['./app.component.scss'],
})
export class AppComponent extends SubscriberBase implements AfterViewInit {
savedPages$ = this.appService.select((state) => state.savedPages.value);
savedPages$ = this.appService.select((state) => (state.savedPages === null ? null : state.savedPages.value));
mainPages$ = this.appService.select((state) => state.mainPages);
fontSize$ = this.appService.select((state) => state.displaySettings.value.cardFontSize + 'pt');
cardFont$ = this.appService.select((state) => state.displaySettings.value.cardFontFamily);

View File

@ -51,6 +51,9 @@ import { ClipboardModule } from '@angular/cdk/clipboard';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { SavedPagesAdminPage } from './pages/saved-pages-admin/saved-pages-admin.page';
import { SavedPageCardComponent } from './components/saved-page-card/saved-page-card.component';
import { SearchPage } from './pages/search/search.page';
import { OkCancelModalComponent } from './components/ok-cancel-modal/ok-cancel-modal.component';
@ -70,6 +73,8 @@ import { AddToPageModalComponent } from './components/add-to-page-modal/add-to-p
@NgModule({
declarations: [
AppComponent,
SavedPagesAdminPage,
SavedPageCardComponent,
SearchPage,
PassageCardComponent,
StrongsComponent,

View File

@ -0,0 +1,34 @@
<div class="card-title pages-title">
<mat-icon aria-hidden="false" aria-label="Saved Page Icon">{{
icon$ | async
}}</mat-icon>
<span *ngIf="savedPage">Page: {{ savedPage.title }}</span>
</div>
<div class="card-content" *ngIf="savedPage">
<mat-nav-list>
<mat-list-item [disableRipple]="true" *ngFor="let q of savedPage.queries">
<span matLine>{{ format(q) }}</span>
<button mat-icon-button (click)="onRemoveCard(q)">
<mat-icon>delete</mat-icon>
</button>
</mat-list-item>
</mat-nav-list>
</div>
<div class="card-actions">
<span class="card-actions-left"> </span>
<span class="card-actions-right">
<button
mat-icon-button
[matMenuTriggerFor]="moreMenu"
aria-label="Example icon-button with a menu"
>
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #moreMenu="matMenu">
<button mat-menu-item (click)="onRemovePage()">
<mat-icon>delete</mat-icon>
<span>Delete Saved Page</span>
</button>
</mat-menu>
</span>
</div>

View File

@ -0,0 +1,15 @@
.pages-title {
background-color: var(--page-color-primary);
}
.card-actions {
color: var(--page-color-primary);
}
.card-content {
overflow-y: auto;
max-height: 25rem;
font-family: var(--card-font-family);
font-size: var(--card-font-size);
padding: 0.5rem;
}

View File

@ -0,0 +1,85 @@
import { Component, ElementRef, ChangeDetectionStrategy, Input, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { AppService } from '../../services/app.service';
import { Observable } from 'rxjs';
import { SavedPage } from 'src/app/models/page-state';
import { CardItem, CardType } from 'src/app/models/card-state';
import { NoteItem } from 'src/app/models/note-state';
import { OkCancelModalComponent, OkCancelResult } from '../ok-cancel-modal/ok-cancel-modal.component';
import { MatSnackBar } from '@angular/material/snack-bar';
@Component({
selector: 'app-saved-page-card',
templateUrl: 'saved-page-card.component.html',
styleUrls: ['./saved-page-card.component.scss'],
preserveWhitespaces: true,
})
export class SavedPageCardComponent implements OnInit {
icon$: Observable<string>;
@Input()
savedPage: SavedPage;
constructor(protected appService: AppService, public dialog: MatDialog, private snackBar: MatSnackBar) {
this.icon$ = appService.select((state) => state.cardIcons.savedPage);
}
format(item: CardItem) {
if (item.type === CardType.Note) {
return `Note: ${(item.data as NoteItem).title}`;
} else if (item.type === CardType.Passage) {
return `Passage: ${item.qry}`;
} else if (item.type === CardType.Strongs) {
return `Strongs: ${item.qry}`;
} else if (item.type === CardType.Word) {
return `Word Search: ${item.qry}`;
}
return '';
}
onRemoveCard(card: CardItem) {
this.dialog
.open(OkCancelModalComponent, {
data: {
title: 'Delete Card from Saved Page',
content: `Do you want to delete this card '${this.format(card)}' from the saved page '${
this.savedPage.title
}?`,
},
})
.afterClosed()
.subscribe((ds: OkCancelResult) => {
if (ds.ok) {
this.appService.updateSavedPage({
...this.savedPage,
queries: this.savedPage.queries.filter((o) => o !== card),
});
this.snackBar.open(`${this.savedPage.title} has been updated!`, '', {
duration: 3 * 1000,
});
}
});
}
onRemovePage() {
this.dialog
.open(OkCancelModalComponent, {
data: {
title: 'Delete Saved Page',
content: `Do you want to delete the saved page '${this.savedPage.title}?`,
},
})
.afterClosed()
.subscribe((ds: OkCancelResult) => {
if (ds.ok) {
this.appService.removeSavedPage(this.savedPage);
this.snackBar.open(`${this.savedPage.title} has been deleted!`, '', {
duration: 3 * 1000,
});
}
});
}
ngOnInit(): void {
console.log(this.savedPage);
}
}

View File

@ -13,7 +13,7 @@
.card-content {
overflow-y: auto;
max-height: 25rem;
font-family: var(--card-font);
font-family: var(--card-font-family);
font-size: var(--card-font-size);
padding: 0.5rem;
}

View File

@ -1,10 +1,12 @@
export const PageTitles = {
Search: 'Search',
PageAdmin: 'Page Admin',
Help: 'Help',
};
export const PageIcons = {
Search: 'search',
PageAdmin: 'library_books',
Help: 'help',
};

View File

@ -24,4 +24,5 @@ export interface CardIcons {
readonly passage: string;
readonly strongs: string;
readonly note: string;
readonly savedPage: string;
}

View File

@ -0,0 +1,16 @@
<mat-toolbar>
<button type="button" mat-icon-button (click)="navService.toggleNav()">
<mat-icon md-48 aria-hidden="false" aria-label="Menu Toggle">menu</mat-icon>
</button>
<span class="page-title">Saved Pages Admin</span>
</mat-toolbar>
<div class="page-content">
<ng-container *ngIf="savedPages$ | async as pages; else nopages">
<mat-card *ngFor="let page of pages">
<app-saved-page-card [savedPage]="page"></app-saved-page-card>
</mat-card>
</ng-container>
<ng-template #nopages>
<span>&nbsp;</span>
</ng-template>
</div>

View File

@ -0,0 +1,20 @@
mat-card {
max-width: 800px;
margin: 1.5rem auto;
padding: 0;
}
.page-title {
width: 100%;
position: relative;
margin-left: 0.8rem;
margin-right: 0.8rem;
}
.page-content {
padding: 0 1rem 0 1rem;
overflow-y: scroll;
height: calc(100vh - 66px);
width: calc(100% - 15px);
margin-top: 2px;
}

View File

@ -0,0 +1,20 @@
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { AppService } from '../../services/app.service';
import { NavService } from '../../services/nav.service';
import { SubscriberBase } from '../../common/subscriber-base';
@Component({
selector: 'app-saved-pages-admin',
templateUrl: './saved-pages-admin.page.html',
styleUrls: ['./saved-pages-admin.page.scss'],
})
export class SavedPagesAdminPage extends SubscriberBase implements OnInit {
savedPages$ = this.appService.select((state) => (state.savedPages === null ? null : state.savedPages.value));
constructor(public navService: NavService, private appService: AppService, private dialog: MatDialog) {
super();
}
ngOnInit() {}
}

View File

@ -54,7 +54,6 @@ export class SearchPage extends SubscriberBase implements OnInit {
if (evt instanceof NavigationEnd) {
this.init();
}
console.log(evt);
})
);
@ -75,8 +74,6 @@ export class SearchPage extends SubscriberBase implements OnInit {
const id = this.activatedRoute.snapshot.paramMap.get('id');
this.onSavedPagedLoaded(id);
this.navService.closeNav(); // close the nav immediately.
} else {
// its a blank search. load nothing.
}
}

View File

@ -33,6 +33,20 @@ export class AppActionFactory {
} as AppAction;
}
static newUpdateSavedPage(savedPage: SavedPage) {
return {
type: 'UPDATE_SAVED_PAGE',
savedPage,
} as AppAction;
}
static newRemoveSavedPage(savedPage: SavedPage) {
return {
type: 'REMOVE_SAVED_PAGE',
savedPage,
} as AppAction;
}
static newAddCardToSavedPage(card: CardItem, pageId: string) {
return {
type: 'ADD_CARD_TO_SAVED_PAGE',
@ -167,6 +181,14 @@ export type AppAction =
type: 'UPDATE_SAVED_PAGES';
savedPages: IStorable<readonly SavedPage[]>;
}
| {
type: 'UPDATE_SAVED_PAGE';
savedPage: SavedPage;
}
| {
type: 'REMOVE_SAVED_PAGE';
savedPage: SavedPage;
}
| {
type: 'ADD_CARD_TO_SAVED_PAGE';
card: CardItem;

View File

@ -22,17 +22,12 @@ export const initialState: AppState = {
],
autocomplete: [],
currentSavedPage: null,
savedPages: {
createdOn: new Date(0).toISOString(),
value: [],
},
notes: {
createdOn: new Date(0).toISOString(),
value: [],
},
savedPages: null,
notes: null,
savedPagesLoaded: false,
mainPages: [
{ title: PageTitles.Search, icon: PageIcons.Search, route: 'search' },
{ title: PageTitles.PageAdmin, icon: PageIcons.PageAdmin, route: 'saved/admin' },
{ title: PageTitles.Help, icon: PageIcons.Help, route: 'help' },
],
error: null,
@ -63,5 +58,6 @@ export const initialState: AppState = {
passage: 'menu_book',
strongs: 'speaker_notes',
note: 'text_snippet',
savedPage: 'library_books',
},
};

View File

@ -17,8 +17,18 @@ function maybeMutateStorable<T>(
incumbant: IStorable<T>,
composeState: (item: IStorable<T>) => AppState
): AppState {
// if the candidate is null, then return the state.
if (!candidate) {
return state;
}
// if the incumbant is null, then def use the candidate.
if (!incumbant) {
return composeState(candidate);
}
// only update if the settings are newer.
if (new Date(candidate.createdOn) > new Date(incumbant.createdOn)) {
if (!incumbant || new Date(candidate.createdOn) > new Date(incumbant.createdOn)) {
return composeState(candidate);
}
@ -83,6 +93,36 @@ export function reducer(state: AppState, action: AppAction): AppState {
};
});
}
case 'UPDATE_SAVED_PAGE': {
const savedPages = new Storable<SavedPage[]>(
state.savedPages.value.map((o) => {
if (o.id === action.savedPage.id) {
return action.savedPage;
}
return o;
})
);
return maybeMutateStorable(state, savedPages, state.savedPages, (item) => {
return {
...state,
savedPagesLoaded: true,
savedPages: item,
};
});
}
case 'REMOVE_SAVED_PAGE': {
const savedPages = new Storable<SavedPage[]>(state.savedPages.value.filter((o) => o.id !== action.savedPage.id));
return maybeMutateStorable(state, savedPages, state.savedPages, (item) => {
return {
...state,
savedPagesLoaded: true,
savedPages: item,
};
});
}
case 'UPDATE_CURRENT_PAGE': {
const savedPages = new Storable<SavedPage[]>([
...state.savedPages.value.filter((o) => o.id !== state.currentSavedPage.id),

View File

@ -111,6 +111,19 @@ export class AppService extends createStateService(reducer, initialState) {
});
}
updateSavedPage(savedPage: SavedPage) {
this.dispatch({
type: 'UPDATE_SAVED_PAGE',
savedPage,
});
}
removeSavedPage(savedPage: SavedPage) {
this.dispatch({
type: 'REMOVE_SAVED_PAGE',
savedPage,
});
}
addCardToSavedPage(pageId: string, card: CardItem) {
this.dispatch({
type: 'ADD_CARD_TO_SAVED_PAGE',

View File

@ -18,7 +18,7 @@ export class StorageService extends SubscriberBase {
private displaySettingsRemoteObject: AngularFireObject<IStorable<DisplaySettings>>;
private savedPagesState$ = this.appService.select((state) => state.savedPages);
private savedPagesPath = 'savedPaged';
private savedPagesPath = 'savedPages';
private savedPagesRemoteObject: AngularFireObject<IStorable<readonly SavedPage[]>>;
private noteItemsState$ = this.appService.select((state) => state.notes);
@ -34,7 +34,7 @@ export class StorageService extends SubscriberBase {
this.addSubscription(
this.displaySettingsState$.subscribe((settings) => {
if (!settings) {
if (!settings || settings.createdOn === new Date(0).toISOString()) {
return;
}

View File

@ -21,6 +21,9 @@ html {
--note-color-primary: rgb(71, 1, 54);
--note-color-accent: rgb(165, 86, 145);
--page-color-primary: rgb(46, 42, 54);
--page-color-accent: rgb(111, 109, 116);
}
body {