lots of fixes for storing notes

This commit is contained in:
Jason Wall 2020-08-07 22:40:55 -04:00
parent 8ed0da3c1d
commit c66cdfb447
11 changed files with 259 additions and 116 deletions

View File

@ -33,7 +33,6 @@ To get more help on the Angular CLI use `ng help` or go check out the [Angular C
- Options to merge references \*\*
- Merge if overlap
- Merge if contains
- Font Size
- Page Admin \*\*
- Delete Page
- Show page and list of card titles
@ -47,7 +46,6 @@ To get more help on the Angular CLI use `ng help` or go check out the [Angular C
- Note: Auto link passages using markdown link syntax
- Passage
- Show Note Cross References \*\*
- Login
- Help Page
- Settings for theme
- Custom Colors for Light/Dark modes

View File

@ -40,13 +40,12 @@ export class AppComponent extends SubscriberComponent implements AfterViewInit {
this.storageService.initSavedPages();
this.storageService.initDisplaySettings();
this.storageService.initNotes();
this.addSubscription(
this.error$.subscribe((err) => {
if (err) {
this.snackBar.open(`Oh no! ${err.msg}`, 'Error', {
duration: 4 * 1000,
});
this.snackBar.open(`Oh no! ${err.msg}`, 'Dismiss Error');
}
})
);

View File

@ -3,6 +3,7 @@ import { IStorable } from './storable';
export interface AppState {
readonly currentSavedPage: SavedPage;
readonly savedPages: IStorable<readonly SavedPage[]>;
readonly notes: IStorable<readonly NoteItem[]>;
readonly savedPagesLoaded: boolean;
readonly mainPages: readonly Page[];
readonly cards: readonly CardItem[];

View File

@ -10,6 +10,10 @@
<mat-label>Title</mat-label>
<input formControlName="title" matInput />
</mat-form-field>
<mat-form-field class="note-xrefs">
<mat-label>References</mat-label>
<input formControlName="xref" matInput />
</mat-form-field>
<mat-form-field class="note-content">
<mat-label>Content</mat-label>
<textarea formControlName="content" matInput></textarea>

View File

@ -40,30 +40,12 @@ export class NoteEditModalComponent {
}
save() {
if (this.isNew) {
this.appService.createNote({
qry: '',
dict: 'n/a',
type: 'Note',
data: {
...this.data,
title: this.noteForm.get('title').value,
content: this.noteForm.get('content').value,
},
});
} 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.appService.saveNote({
...this.data,
title: this.noteForm.get('title').value,
xref: this.noteForm.get('xref').value,
content: this.noteForm.get('content').value,
});
this.dialogRef.close();
}

View File

@ -16,6 +16,14 @@
<ngx-md class="markdown" *ngIf="cardItem.data">{{
cardItem.data.content
}}</ngx-md>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
Cross References
</mat-panel-title>
</mat-expansion-panel-header>
{{ cardItem.data.xref }}
</mat-expansion-panel>
</div>
<div class="card-actions">
<span class="card-actions-left">

View File

@ -3,6 +3,7 @@ import { MatDialog } from '@angular/material/dialog';
import { NoteEditModalComponent } from './edit-modal/note-edit-modal.component';
import { CardComponent } from '../../../common/components/card.component';
import { AppService } from '../../../services/app.service';
import { NoteItem } from 'src/app/models/app-state';
@Component({
selector: 'app-note-card',
@ -12,11 +13,7 @@ import { AppService } from '../../../services/app.service';
export class NoteCardComponent extends CardComponent {
@ViewChild('note') noteElement: ElementRef;
constructor(
protected elementRef: ElementRef,
private appService: AppService,
public dialog: MatDialog
) {
constructor(protected elementRef: ElementRef, private appService: AppService, public dialog: MatDialog) {
super(elementRef, dialog);
this.icon$ = appService.select((state) => state.cardIcons.note);
@ -35,6 +32,6 @@ export class NoteCardComponent extends CardComponent {
}
delete() {
this.appService.deleteNote(this.cardItem);
this.appService.deleteNote(this.cardItem.data as NoteItem);
}
}

View File

@ -1,5 +1,5 @@
<mat-toolbar>
<button type="button" mat-icon-button (click)="navService.toggle()">
<button type="button" mat-icon-button (click)="navService.toggleNav()">
<mat-icon md-48 aria-hidden="false" aria-label="Menu Toggle">menu</mat-icon>
</button>
<div class="search-bar">

View File

@ -119,12 +119,7 @@ export class SearchPage extends SubscriberComponent implements OnInit {
const q = term.trim();
if (q !== '') {
if (q.startsWith('note:')) {
// // It's a note lookup
// list.push({
// qry: q.replace('note:', ''),
// dict: '',
// type: 'Note',
// });
await this.appService.getNote(q.replace('note:', ''));
} else if (q.search(/[0-9]/i) === -1) {
// // its a search term.
await this.appService.getWords(q);

View File

@ -40,7 +40,7 @@ const initialState: AppState = {
type: 'Note',
data: {
id: UUID.UUID(),
xref: null,
xref: '',
title: 'Title Here',
content: '# Content Here\nIn Markdown format.',
},
@ -52,6 +52,10 @@ const initialState: AppState = {
createdOn: new Date(0).toISOString(),
value: [],
},
notes: {
createdOn: new Date(0).toISOString(),
value: [],
},
savedPagesLoaded: false,
mainPages: [
{ title: PageTitles.Search, icon: PageIcons.Search, route: 'search' },
@ -140,6 +144,23 @@ type AppAction =
| {
type: 'SET_USER';
user: User;
}
| {
type: 'FIND_NOTES';
qry: string;
nextToItem: CardItem;
}
| {
type: 'UPDATE_NOTES';
notes: IStorable<readonly NoteItem[]>;
}
| {
type: 'SAVE_NOTE';
note: NoteItem;
}
| {
type: 'DELETE_NOTE';
note: NoteItem;
};
function maybeMutateStorable<T>(
@ -332,6 +353,136 @@ function reducer(state: AppState, action: AppAction): AppState {
user: action.user,
};
}
case 'FIND_NOTES': {
const notes = state.notes.value
.filter((o) => o.title.search(action.qry) > -1)
.map((o) => {
return {
qry: o.id,
dict: 'n/a',
type: 'Note',
data: o,
};
});
let cards = [];
if (action.nextToItem && state.displaySettings.value.insertCardNextToItem) {
const idx = state.cards.indexOf(action.nextToItem);
if (state.displaySettings.value.appendCardToBottom) {
const before = state.cards.slice(0, idx + 1);
const after = state.cards.slice(idx + 1);
cards = [...before, ...notes, ...after];
} else {
const before = state.cards.slice(0, idx);
const after = state.cards.slice(idx);
cards = [...before, ...notes, ...after];
}
} else {
if (state.displaySettings.value.appendCardToBottom) {
cards = [...state.cards, ...notes];
} else {
cards = [...notes, ...state.cards];
}
}
return {
...state,
cards,
};
}
case 'UPDATE_NOTES': {
return {
...state,
notes: action.notes,
};
}
case 'SAVE_NOTE': {
// you may be creating a new note or updating an existing.
// if its an update, you need to update the note in the following:
// * card list could have it.
// * notes list could have it.
// * it could be in any of the saved pages lists...
// so iterate through all of them and if you find the note
// in any of them, swap it out
const cards = [
...state.cards.map((o) => {
const n = o.data as NoteItem;
if (n && n.id === action.note.id) {
return {
...o,
data: action.note,
};
}
return o;
}),
];
const notes = new Storable<NoteItem[]>([
...state.notes.value.filter((o) => o.id === action.note.id),
action.note,
]);
const savedPages = new Storable<SavedPage[]>([
...state.savedPages.value.map((sp) => {
return {
...sp,
queries: sp.queries.map((o) => {
const n = o.data as NoteItem;
if (n && n.id === action.note.id) {
return {
...o,
data: action.note,
};
}
return o;
}),
};
}),
]);
return {
...state,
cards,
notes,
savedPages,
};
}
case 'DELETE_NOTE': {
// the note may be in any of the following:
// * card list could have it.
// * notes list could have it.
// * it could be in any of the saved pages lists...
// so iterate through all of them and if you find the note
// in any of them, remove it
const cards = [
...state.cards.filter((o) => {
const n = o.data as NoteItem;
return !n || n.id !== action.note.id;
}),
];
const notes = new Storable<NoteItem[]>([...state.notes.value.filter((o) => o.id !== action.note.id)]);
const savedPages = new Storable<SavedPage[]>([
...state.savedPages.value.map((sp) => {
return {
...sp,
queries: sp.queries.filter((o) => {
const n = o.data as NoteItem;
return !n || n.id !== action.note.id;
}),
};
}),
]);
return {
...state,
cards,
notes,
savedPages,
};
}
}
}
@ -442,11 +593,19 @@ export class AppService extends createStateService(reducer, initialState) {
//#region Notes
async getNote(qry: string, nextToItem: CardItem = null) {
const note = (await this.localStorageService.get('notes/' + qry).toPromise()) as NoteItem;
findNotes(qry: string, nextToItem: CardItem = null) {
this.dispatch({
type: 'FIND_NOTES',
qry,
nextToItem,
});
}
async getNote(id: string, nextToItem: CardItem = null) {
const note = (await this.localStorageService.get('notes/' + id).toPromise()) as NoteItem;
const card = {
qry,
qry: id,
dict: 'n/a',
type: 'Note',
data: note,
@ -459,77 +618,25 @@ 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. :(",
},
});
}
);
updateNotes(notes: IStorable<readonly NoteItem[]>) {
this.dispatch({
type: 'UPDATE_NOTES',
notes,
});
}
async editNote(newCard: CardItem, oldCard: CardItem) {
this.saveNoteApi(newCard.data as NoteItem).subscribe(
// success
() => {
this.dispatch({
type: 'UPDATE_CARD',
newCard,
oldCard,
});
},
// error
() => {
this.dispatch({
type: 'UPDATE_ERROR',
error: {
// tslint:disable-next-line: quotemark
msg: "Something went wrong and the note wasn't saved. :(",
},
});
}
);
saveNote(note: NoteItem, nextToItem: CardItem = null) {
this.dispatch({
type: 'SAVE_NOTE',
note,
});
}
async deleteNote(noteCard: CardItem) {
this.deleteNoteApi(noteCard.data as NoteItem).subscribe(
// success
() => {
this.removeCard(noteCard);
},
// error
() => {
this.dispatch({
type: 'UPDATE_ERROR',
error: {
// tslint:disable-next-line: quotemark
msg: "Something went wrong and the note wasn't saved. :(",
},
});
}
);
}
private deleteNoteApi(note: NoteItem) {
return this.localStorageService.delete('notes/' + note.id);
}
private saveNoteApi(note: NoteItem) {
return this.localStorageService.set('notes/' + note.id, note);
deleteNote(note: NoteItem) {
this.dispatch({
type: 'DELETE_NOTE',
note,
});
}
//#endregion

View File

@ -3,7 +3,7 @@ import { StorageMap } from '@ngx-pwa/local-storage';
import { AngularFireDatabase, AngularFireObject } from '@angular/fire/database';
import { IStorable } from '../models/storable';
import { AppService } from './app.service';
import { DisplaySettings, SavedPage, User } from '../models/app-state';
import { DisplaySettings, SavedPage, User, NoteItem } from '../models/app-state';
import { SubscriberComponent } from '../common/components/subscriber.component';
@Injectable({
@ -18,6 +18,10 @@ export class StorageService extends SubscriberComponent {
private savedPagesPath = 'savedPaged';
private savedPagesRemoteObject: AngularFireObject<IStorable<readonly SavedPage[]>>;
private noteItemsState$ = this.appService.select((state) => state.notes);
private noteItemsPath = 'noteItems';
private noteItemsRemoteObject: AngularFireObject<IStorable<readonly NoteItem[]>>;
constructor(private local: StorageMap, private remote: AngularFireDatabase, private appService: AppService) {
super();
@ -32,7 +36,7 @@ export class StorageService extends SubscriberComponent {
}
// update local
this.local.set('displaySettings', settings).subscribe(
this.local.set(this.displaySettingsPath, settings).subscribe(
() => {
// nop
},
@ -57,7 +61,7 @@ export class StorageService extends SubscriberComponent {
}
// update local
this.local.set('savedPages', savedPages).subscribe(
this.local.set(this.savedPagesPath, savedPages).subscribe(
() => {
// nop
},
@ -75,6 +79,31 @@ export class StorageService extends SubscriberComponent {
})
);
this.addSubscription(
this.noteItemsState$.subscribe((notes) => {
if (!notes) {
return;
}
// update local
this.local.set(this.noteItemsPath, notes).subscribe(
() => {
// nop
},
// error
() => {
// tslint:disable-next-line: quotemark
this.appService.dispatchError("Something went wrong and the note wasn't saved. :(");
}
);
// update remote
if (this.noteItemsRemoteObject) {
this.noteItemsRemoteObject.set(notes);
}
})
);
//#endregion
}
@ -83,6 +112,7 @@ export class StorageService extends SubscriberComponent {
`/${this.displaySettingsPath}/${user.uid}`
);
this.savedPagesRemoteObject = this.remote.object<IStorable<SavedPage[]>>(`/${this.savedPagesPath}/${user.uid}`);
this.noteItemsRemoteObject = this.remote.object<IStorable<NoteItem[]>>(`/${this.noteItemsPath}/${user.uid}`);
// display settings
this.addSubscription(
@ -107,6 +137,18 @@ export class StorageService extends SubscriberComponent {
}
})
);
// note items
this.addSubscription(
this.noteItemsRemoteObject
.valueChanges() // when the saved pages have changed
.subscribe((remoteNoteItems) => {
if (remoteNoteItems) {
// update the saved pages locally from remote if it isn't null
this.appService.updateNotes(remoteNoteItems);
}
})
);
}
async initDisplaySettings() {
@ -128,4 +170,14 @@ export class StorageService extends SubscriberComponent {
this.appService.updateSavedPages(savedPages);
}
}
async initNotes() {
const exists = await this.local.has(this.noteItemsPath).toPromise();
if (exists) {
const notes = (await this.local.get(this.noteItemsPath).toPromise()) as IStorable<NoteItem[]>;
this.appService.updateNotes(notes);
}
}
}