diff --git a/package.json b/package.json index 20725ff..18f2ce5 100644 --- a/package.json +++ b/package.json @@ -34,14 +34,19 @@ "zone.js": "~0.10.2" }, "devDependencies": { + "@angular-devkit/architect": "<0.900 || ^0.900.0-0 || ^9.0.0-0", "@angular-devkit/build-angular": "~0.901.1", "@angular/cli": "~9.1.1", "@angular/compiler-cli": "~9.1.2", "@angular/language-service": "~9.1.2", - "@types/node": "^12.11.1", "@types/jasmine": "~3.3.8", "@types/jasminewd2": "~2.0.3", + "@types/node": "^12.12.36", "codelyzer": "^5.1.2", + "firebase-tools": "^7.12.0", + "fuzzy": "^0.1.3", + "inquirer": "^6.2.2", + "inquirer-autocomplete-prompt": "^1.0.1", "jasmine-core": "~3.4.0", "jasmine-spec-reporter": "~4.2.1", "karma": "~4.1.0", @@ -52,11 +57,6 @@ "protractor": "~5.4.0", "ts-node": "~7.0.0", "tslint": "~5.15.0", - "typescript": "~3.8.3", - "@angular-devkit/architect": "<0.900 || ^0.900.0-0 || ^9.0.0-0", - "firebase-tools": "^7.12.0", - "fuzzy": "^0.1.3", - "inquirer": "^6.2.2", - "inquirer-autocomplete-prompt": "^1.0.1" + "typescript": "~3.8.3" } } diff --git a/src/app/modules/shows/services/docx.service.ts b/src/app/modules/shows/services/docx.service.ts index a7f7718..52c9575 100644 --- a/src/app/modules/shows/services/docx.service.ts +++ b/src/app/modules/shows/services/docx.service.ts @@ -1,10 +1,170 @@ import {Injectable} from '@angular/core'; +import {Document, HeadingLevel, Packer, Paragraph} from 'docx'; +import {ShowService} from './show.service'; +import {ShowTypePipe} from '../../../widget-modules/pipes/show-type-translater/show-type.pipe'; +import {first} from 'rxjs/operators'; +import {ShowSongService} from './show-song.service'; +import {Line, LineType, Section, TextRenderingService} from '../../songs/services/text-rendering.service'; +import {Song} from '../../songs/services/song'; +import {SongService} from '../../songs/services/song.service'; +import {ShowSong} from './showSong'; +import {Show} from './show'; +import {ChordMode} from '../../../widget-modules/components/song-text/song-text.component'; + @Injectable({ providedIn: 'root' }) export class DocxService { - constructor() { + constructor( + private showService: ShowService, + private showSongService: ShowSongService, + private songService: SongService, + private textRenderingService: TextRenderingService + ) { + } + + public async create(showId: string): Promise { + const {show, songs} = await this.prepareData(showId); + + const paragraphs = [ + ...this.renderTitle(show), + ...this.renderSongs(songs), + ]; + console.log(paragraphs); + + const document = this.prepareNewDocument(); + document.addSection({ + properties: {top: 400, bottom: 400, left: 400, right: 400}, + children: paragraphs, + }); + + + const blob = await Packer.toBlob(document); + + // saveAs from FileSaver will download the file + this.saveAs(blob, 'example.docx'); + } + + private prepareNewDocument(): Document { + return new Document({ + creator: 'TODO', + title: 'Gottesdienst', + description: '... mit Beschreibung', + styles: { + paragraphStyles: [ + { + id: 'songtext', + name: 'Song Text', + basedOn: 'Normal', + next: 'Normal', + quickFormat: true, + run: {font: 'courier new'}, + paragraph: {indent: {left: 0}}, + }, + ] + } + }) + } + + private renderSongs(songs: { showSong: ShowSong; song: Song; sections: Section[] }[]): Paragraph[] { + return songs.reduce((p, song) => [...p, ...this.renderSong(song.showSong, song.song, song.sections)], []) + } + + private renderSong(showSong: ShowSong, song: Song, sections: Section[]): Paragraph[] { + const songTitle = this.renderSongTitle(song); + const songText = this.renderSongText(sections, showSong.chordMode); + + return [ + songTitle, + ...songText + ] + } + + private renderSongText(sections: Section[], chordMode: ChordMode) { + return sections.reduce((p, section) => [...p, ...this.renderSection(section, chordMode)], []); + } + + private renderSongTitle(song: Song): Paragraph { + return new Paragraph({ + text: song.title, + heading: HeadingLevel.HEADING_2, + thematicBreak: true, + spacing: { + before: 200, + }, + }); + } + + + private renderSection(section: Section, chordMode: ChordMode): Paragraph[] { + return section.lines + .filter(line => { + if (line.type === LineType.text) return true; + switch (chordMode) { + case 'show': + return true; + case 'hide': + return false; + case 'onlyFirst': + return section.number === 0; + } + }) + .map((line, i) => this.renderLine(line, i === 0)); + } + + private renderLine(line: Line, isFirstLine: boolean): Paragraph { + const spacing = isFirstLine ? {before: 200} : {}; + return new Paragraph({ + text: line.text, + style: 'songtext', + spacing + }); + + } + + private renderTitle(show: Show): Paragraph[] { + const type = new ShowTypePipe().transform(show.showType); + + const songTitle = new Paragraph({ + text: type, + heading: HeadingLevel.HEADING_1, + thematicBreak: true, + }); + + return [songTitle] + } + + private async prepareData(showId: string): Promise<{ songs: ({ showSong: ShowSong, song: Song, sections: Section[] })[]; show: Show }> { + const show = await this.showService.read$(showId).pipe(first()).toPromise(); + + const showSongs = await this.showSongService.list$(showId).pipe(first()).toPromise(); + const songsAsync = await showSongs.map(async showSong => { + const song = await this.songService.read(showSong.songId).pipe(first()).toPromise(); + const sections = this.textRenderingService.parse(song.text); + return { + showSong, + song, + sections + } + }) + const songs = await Promise.all(songsAsync); + return {songs, show}; + } + + private saveAs(blob, fileName) { + const a = document.createElement('a') as any; + document.body.appendChild(a); + a.style = 'display: none'; + + const url = window.URL.createObjectURL(blob); + a.href = url; + a.download = fileName; + a.click(); + window.URL.revokeObjectURL(url); + + document.body.removeChild(a); + }; } diff --git a/src/app/modules/shows/show/show.component.html b/src/app/modules/shows/show/show.component.html index b4cb3eb..fc6a02f 100644 --- a/src/app/modules/shows/show/show.component.html +++ b/src/app/modules/shows/show/show.component.html @@ -31,6 +31,9 @@ + + + diff --git a/src/app/modules/shows/show/show.component.ts b/src/app/modules/shows/show/show.component.ts index 5d1a765..e893255 100644 --- a/src/app/modules/shows/show/show.component.ts +++ b/src/app/modules/shows/show/show.component.ts @@ -9,6 +9,7 @@ import {Song} from '../../songs/services/song'; import {MatSelectChange} from '@angular/material/select'; import {ShowSongService} from '../services/show-song.service'; import {ShowSong} from '../services/showSong'; +import {DocxService} from '../services/docx.service'; @Component({ selector: 'app-show', @@ -27,6 +28,7 @@ export class ShowComponent implements OnInit { private showService: ShowService, private songService: SongService, private showSongService: ShowSongService, + private docxService: DocxService, ) { } @@ -83,4 +85,8 @@ export class ShowComponent implements OnInit { if (show.reported) return 'gemeldet'; return 'entwurf'; } + + public async onDownload(): Promise { + await this.docxService.create(this.showId); + } } diff --git a/src/manifest.webmanifest b/src/manifest.webmanifest index df897c5..cc5942c 100644 --- a/src/manifest.webmanifest +++ b/src/manifest.webmanifest @@ -12,5 +12,5 @@ "sizes": "192x192", "type": "image/png" } - ], + ] } diff --git a/tsconfig.app.json b/tsconfig.app.json index df4d05e..589bd9c 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -2,7 +2,9 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/app", - "types": [] + "types": [ + "node" + ] }, "files": [ "src/main.ts",