search function for show editing

This commit is contained in:
2020-04-21 18:43:08 +02:00
committed by smuddy
parent 5695640fe4
commit c526f127e8
10 changed files with 73 additions and 35 deletions

8
package-lock.json generated
View File

@@ -11122,6 +11122,14 @@
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=",
"dev": true "dev": true
}, },
"ngx-mat-select-search": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ngx-mat-select-search/-/ngx-mat-select-search-2.1.2.tgz",
"integrity": "sha512-LH6g51PwBsKFu9oOh5RQkC3XwE8InUbNVhHYA6PSUKPNNVx89fpHLXXnd6P/+XztL4+DNTVDeo/XzreIq7Pa4g==",
"requires": {
"tslib": "^1.9.0"
}
},
"nice-try": { "nice-try": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",

View File

@@ -29,6 +29,7 @@
"@fortawesome/free-solid-svg-icons": "^5.13.0", "@fortawesome/free-solid-svg-icons": "^5.13.0",
"docx": "^5.0.2", "docx": "^5.0.2",
"firebase": ">= 5.5.7 <8", "firebase": ">= 5.5.7 <8",
"ngx-mat-select-search": "^2.1.2",
"rxjs": "~6.5.5", "rxjs": "~6.5.5",
"tslib": "^1.10.0", "tslib": "^1.10.0",
"zone.js": "~0.10.2" "zone.js": "~0.10.2"

View File

@@ -10,6 +10,8 @@ import {SongService} from '../../songs/services/song.service';
import {ShowSong} from './showSong'; import {ShowSong} from './showSong';
import {Show} from './show'; import {Show} from './show';
import {ChordMode} from '../../../widget-modules/components/song-text/song-text.component'; import {ChordMode} from '../../../widget-modules/components/song-text/song-text.component';
import {UserService} from '../../../services/user.service';
import {User} from '../../../services/user';
@Injectable({ @Injectable({
@@ -21,37 +23,38 @@ export class DocxService {
private showService: ShowService, private showService: ShowService,
private showSongService: ShowSongService, private showSongService: ShowSongService,
private songService: SongService, private songService: SongService,
private textRenderingService: TextRenderingService private textRenderingService: TextRenderingService,
private userService: UserService,
) { ) {
} }
public async create(showId: string): Promise<any> { public async create(showId: string): Promise<any> {
const {show, songs} = await this.prepareData(showId); const {show, songs, user} = await this.prepareData(showId);
const type = new ShowTypePipe().transform(show.showType);
const title = `${type} ${show.date.toDate().toLocaleDateString()}`;
const paragraphs = [ const paragraphs = [
...this.renderTitle(show), ...this.renderTitle(title),
...this.renderSongs(songs), ...this.renderSongs(songs),
]; ];
console.log(paragraphs); console.log(paragraphs);
const document = this.prepareNewDocument(); const document = this.prepareNewDocument(type, user.name);
document.addSection({ document.addSection({
properties: {top: 400, bottom: 400, left: 400, right: 400}, properties: {top: 400, bottom: 400, left: 400, right: 400},
children: paragraphs, children: paragraphs,
}); });
const blob = await Packer.toBlob(document); const blob = await Packer.toBlob(document);
// saveAs from FileSaver will download the file // saveAs from FileSaver will download the file
this.saveAs(blob, 'example.docx'); this.saveAs(blob, `${title}.docx`);
} }
private prepareNewDocument(): Document { private prepareNewDocument(type: string, name: string): Document {
return new Document({ return new Document({
creator: 'TODO', creator: name,
title: 'Gottesdienst', title: type,
description: '... mit Beschreibung', description: '... mit Beschreibung',
styles: { styles: {
paragraphStyles: [ paragraphStyles: [
@@ -125,8 +128,7 @@ export class DocxService {
} }
private renderTitle(show: Show): Paragraph[] { private renderTitle(type: string): Paragraph[] {
const type = new ShowTypePipe().transform(show.showType);
const songTitle = new Paragraph({ const songTitle = new Paragraph({
text: type, text: type,
@@ -137,8 +139,9 @@ export class DocxService {
return [songTitle] return [songTitle]
} }
private async prepareData(showId: string): Promise<{ songs: ({ showSong: ShowSong, song: Song, sections: Section[] })[]; show: Show }> { private async prepareData(showId: string): Promise<{ songs: ({ showSong: ShowSong, song: Song, sections: Section[] })[]; show: Show, user: User }> {
const show = await this.showService.read$(showId).pipe(first()).toPromise(); const show = await this.showService.read$(showId).pipe(first()).toPromise();
const user = await this.userService.getUserbyId$(show.owner).pipe(first()).toPromise();
const showSongs = await this.showSongService.list$(showId).pipe(first()).toPromise(); const showSongs = await this.showSongService.list$(showId).pipe(first()).toPromise();
const songsAsync = await showSongs.map(async showSong => { const songsAsync = await showSongs.map(async showSong => {
@@ -151,20 +154,24 @@ export class DocxService {
} }
}) })
const songs = await Promise.all(songsAsync); const songs = await Promise.all(songsAsync);
return {songs, show}; return {songs, show, user};
} }
private saveAs(blob, fileName) { private saveAs(blob, fileName) {
const a = document.createElement('a') as any; const a = document.createElement('a') as any;
document.body.appendChild(a); document.body.appendChild(a);
a.setAttribute('target', '_self');
a.style = 'display: none'; a.style = 'display: none';
const url = window.URL.createObjectURL(blob); const url = window.URL.createObjectURL(blob);
a.href = url; a.href = url;
a.download = fileName; a.download = fileName;
a.click(); a.click();
window.URL.revokeObjectURL(url);
setTimeout(() => {
window.URL.revokeObjectURL(url);
document.body.removeChild(a); document.body.removeChild(a);
}, 1000);
}; };
} }

View File

@@ -21,7 +21,10 @@
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label>Lied hinzufügen...</mat-label> <mat-label>Lied hinzufügen...</mat-label>
<mat-select (selectionChange)="onAddSongSelectionChanged($event)"> <mat-select (selectionChange)="onAddSongSelectionChanged($event)">
<mat-option *ngFor="let song of songs" [value]="song.id">{{song.title}}</mat-option> <mat-option>
<ngx-mat-select-search [formControl]="filteredSongsControl"></ngx-mat-select-search>
</mat-option>
<mat-option *ngFor="let song of filteredSongs()" [value]="song.id">{{song.title}}</mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>

View File

@@ -10,6 +10,8 @@ import {MatSelectChange} from '@angular/material/select';
import {ShowSongService} from '../services/show-song.service'; import {ShowSongService} from '../services/show-song.service';
import {ShowSong} from '../services/showSong'; import {ShowSong} from '../services/showSong';
import {DocxService} from '../services/docx.service'; import {DocxService} from '../services/docx.service';
import {FormControl} from '@angular/forms';
import {filterSong} from '../../../services/filter.helper';
@Component({ @Component({
selector: 'app-show', selector: 'app-show',
@@ -89,4 +91,11 @@ export class ShowComponent implements OnInit {
public async onDownload(): Promise<void> { public async onDownload(): Promise<void> {
await this.docxService.create(this.showId); await this.docxService.create(this.showId);
} }
public filteredSongsControl = new FormControl();
filteredSongs() {
const filterValue = this.filteredSongsControl.value;
return filterValue ? this.songs.filter(_ => filterSong(_, filterValue)) : this.songs;
}
} }

View File

@@ -22,6 +22,7 @@ import {SongComponent} from './show/song/song.component';
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome'; import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
import {MenuButtonModule} from '../../widget-modules/components/menu-button/menu-button.module'; import {MenuButtonModule} from '../../widget-modules/components/menu-button/menu-button.module';
import {SongTextModule} from '../../widget-modules/components/song-text/song-text.module'; import {SongTextModule} from '../../widget-modules/components/song-text/song-text.module';
import {NgxMatSelectSearchModule} from 'ngx-mat-select-search';
@NgModule({ @NgModule({
@@ -44,7 +45,8 @@ import {SongTextModule} from '../../widget-modules/components/song-text/song-tex
FontAwesomeModule, FontAwesomeModule,
MenuButtonModule, MenuButtonModule,
FormsModule, FormsModule,
SongTextModule SongTextModule,
NgxMatSelectSearchModule,
] ]
}) })
export class ShowsModule { export class ShowsModule {

View File

@@ -5,6 +5,7 @@ import {debounceTime, map} from 'rxjs/operators';
import {combineLatest, Observable} from 'rxjs'; import {combineLatest, Observable} from 'rxjs';
import {fade} from '../../../animations'; import {fade} from '../../../animations';
import {ActivatedRoute} from '@angular/router'; import {ActivatedRoute} from '@angular/router';
import {filterSong} from '../../../services/filter.helper';
@Component({ @Component({
selector: 'app-songs', selector: 'app-songs',
@@ -19,21 +20,6 @@ export class SongListComponent implements OnInit {
constructor(private songService: SongService, private activatedRoute: ActivatedRoute) { constructor(private songService: SongService, private activatedRoute: ActivatedRoute) {
} }
private static filter(song: Song, filterValue: string): boolean {
if (!filterValue) {
return true;
}
const textMatch = song.text && SongListComponent.normalize(song.text).indexOf(SongListComponent.normalize(filterValue)) !== -1;
const titleMatch = song.title && SongListComponent.normalize(song.title).indexOf(SongListComponent.normalize(filterValue)) !== -1;
return textMatch || titleMatch;
}
private static normalize(input: string): string {
return input.toLowerCase().replace(/\s/g, '');
}
ngOnInit() { ngOnInit() {
const filter$ = this.activatedRoute.queryParams.pipe( const filter$ = this.activatedRoute.queryParams.pipe(
debounceTime(300), debounceTime(300),
@@ -45,7 +31,7 @@ export class SongListComponent implements OnInit {
); );
this.songs$ = combineLatest([filter$, songs$]).pipe( this.songs$ = combineLatest([filter$, songs$]).pipe(
map(_ => _[1].filter(song => SongListComponent.filter(song, _[0]))) map(_ => _[1].filter(song => filterSong(song, _[0])))
); );
} }
} }

View File

@@ -0,0 +1,16 @@
import {Song} from '../modules/songs/services/song';
export function filterSong(song: Song, filterValue: string): boolean {
if (!filterValue) {
return true;
}
const textMatch = song.text && normalize(song.text).indexOf(normalize(filterValue)) !== -1;
const titleMatch = song.title && normalize(song.title).indexOf(normalize(filterValue)) !== -1;
return textMatch || titleMatch;
}
function normalize(input: string): string {
return input.toLowerCase().replace(/\s/g, '');
}

View File

@@ -22,6 +22,10 @@ export class UserService {
return this._user$.pipe(filter(_ => !!_)); return this._user$.pipe(filter(_ => !!_));
} }
public getUserbyId$(userId: string): Observable<User> {
return this.db.doc$<User>('user/' + userId);
}
public async update$(uid: string, data: Partial<User>): Promise<void> { public async update$(uid: string, data: Partial<User>): Promise<void> {
await this.db.doc<User>('user/' + uid).update(data); await this.db.doc<User>('user/' + uid).update(data);
} }

View File

@@ -4,7 +4,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<title>Worship Generator</title> <title>Worship Generator</title>
<base href="/"> <base href="/">
<meta content="width=device-width, initial-scale=1" name="viewport"> <meta content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0' name='viewport'/>
<link href="apple-touch-icon.png" rel="apple-touch-icon" sizes="180x180"> <link href="apple-touch-icon.png" rel="apple-touch-icon" sizes="180x180">
<link href="favicon-32x32.png" rel="icon" sizes="32x32" type="image/png"> <link href="favicon-32x32.png" rel="icon" sizes="32x32" type="image/png">
@@ -24,5 +24,7 @@
<script>importCCLI = {}</script> <script>importCCLI = {}</script>
<script>setStrophe = {}</script> <script>setStrophe = {}</script>
<noscript>Please enable JavaScript to continue using this application.</noscript> <noscript>Please enable JavaScript to continue using this application.</noscript>
</body> </body>
</html> </html>