search function for show editing
This commit is contained in:
8
package-lock.json
generated
8
package-lock.json
generated
@@ -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",
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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);
|
|
||||||
|
|
||||||
document.body.removeChild(a);
|
setTimeout(() => {
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
document.body.removeChild(a);
|
||||||
|
}, 1000);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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])))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
16
src/app/services/filter.helper.ts
Normal file
16
src/app/services/filter.helper.ts
Normal 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, '');
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user