diff --git a/.github/workflows/end-to-end-tests.yml b/.github/workflows/end-to-end-tests.yml index bd59c246..1a1fba1b 100644 --- a/.github/workflows/end-to-end-tests.yml +++ b/.github/workflows/end-to-end-tests.yml @@ -113,6 +113,7 @@ jobs: run: npx nx e2e studio-web - name: Upload HTML report + if: always() uses: actions/upload-artifact@v6 with: name: playwright-report--attempt-${{ github.run_attempt }} diff --git a/packages/studio-web/project.json b/packages/studio-web/project.json index a0f58f97..efc71bba 100644 --- a/packages/studio-web/project.json +++ b/packages/studio-web/project.json @@ -84,6 +84,12 @@ "update-browserslist": { "command": "npx -y update-browserslist-db@latest" }, + "just-serve": { + "executor": "@angular-builders/custom-webpack:dev-server", + "options": { + "buildTarget": "studio-web:build:development,en" + } + }, "serve": { "executor": "@angular-builders/custom-webpack:dev-server", "dependsOn": ["update-browserslist"], diff --git a/packages/studio-web/src/app/app.component.ts b/packages/studio-web/src/app/app.component.ts index 9d42d010..9bff06a8 100644 --- a/packages/studio-web/src/app/app.component.ts +++ b/packages/studio-web/src/app/app.component.ts @@ -56,20 +56,21 @@ export class AppComponent implements OnInit { }) export class PrivacyDialog { private dialogRef = inject(MatDialogRef); - protected analyticsExcluded = - window.localStorage.getItem("plausible_ignore") === "true"; + protected analyticsExcluded = signal( + window.localStorage.getItem("plausible_ignore") === "true", + ); ngOnInit() { this.dialogRef.updateSize("100%"); } toggleAnalytics() { - if (this.analyticsExcluded) { + if (this.analyticsExcluded()) { window.localStorage.removeItem("plausible_ignore"); - this.analyticsExcluded = false; + this.analyticsExcluded.set(false); } else { window.localStorage.setItem("plausible_ignore", "true"); - this.analyticsExcluded = true; + this.analyticsExcluded.set(true); } } } diff --git a/packages/studio-web/src/app/editor/editor.component.html b/packages/studio-web/src/app/editor/editor.component.html index 61f9afde..919bf9d6 100644 --- a/packages/studio-web/src/app/editor/editor.component.html +++ b/packages/studio-web/src/app/editor/editor.component.html @@ -22,7 +22,7 @@

- @if (editorService.uploadFormGroup.valid) { + @if (editorService.uploadFormValid()) {
>
void; @@ -99,7 +100,7 @@ export class EditorComponent implements OnDestroy, AfterViewInit { private formIsDirty(): boolean { return ( (this.rasFileUpload && this.rasFileUpload.nativeElement.value !== "") || - this.rasFileIsLoaded + this.rasFileIsLoaded() ); } @@ -217,7 +218,7 @@ export class EditorComponent implements OnDestroy, AfterViewInit { this.wcStylingService, ); } - this.rasFileIsLoaded = false; + this.rasFileIsLoaded.set(false); window.removeEventListener("beforeunload", this.beforeUnload); } @@ -304,7 +305,7 @@ export class EditorComponent implements OnDestroy, AfterViewInit { const readalong = await this.parseReadalong(text); this.loadAudioIntoWavesurferElement(); this.renderReadalong(readalong); - this.rasFileIsLoaded = true; + this.rasFileIsLoaded.set(true); } async renderReadalong(readalongBody: string | undefined) { diff --git a/packages/studio-web/src/app/editor/editor.service.ts b/packages/studio-web/src/app/editor/editor.service.ts index 85d733c8..5f11a912 100644 --- a/packages/studio-web/src/app/editor/editor.service.ts +++ b/packages/studio-web/src/app/editor/editor.service.ts @@ -1,5 +1,7 @@ import { Injectable } from "@angular/core"; import { FormControl, FormGroup, Validators } from "@angular/forms"; +import { toSignal } from "@angular/core/rxjs-interop"; +import { map, startWith } from "rxjs"; import { ReadAlongSlots } from "../ras.service"; @Injectable({ @@ -27,5 +29,14 @@ export class EditorService { ras: this.rasControl$, audioB64: this.audioB64Control$, }); + public uploadFormValid = toSignal( + this.uploadFormGroup.statusChanges.pipe( + startWith(this.uploadFormGroup.status), + map(() => this.uploadFormGroup.valid), + ), + { + initialValue: this.uploadFormGroup.valid, + }, + ); temporaryBlob: Blob | undefined = undefined; } diff --git a/packages/studio-web/src/app/privacy-dialog.html b/packages/studio-web/src/app/privacy-dialog.html index e7a2fc99..5c664540 100644 --- a/packages/studio-web/src/app/privacy-dialog.html +++ b/packages/studio-web/src/app/privacy-dialog.html @@ -34,7 +34,7 @@

Privacy Policy

style="display: flex; justify-content: space-evenly; margin-top: 1em" >
- @if(analyticsExcluded) { + @if(analyticsExcluded()) {
- @if (canUseClipBoard) { + @if (canUseClipBoard()) {
- @if (recording) { + @if (recording()) { } - @if ( - !recording && - studioService.audioControl$.value !== null && - playing - ) { + @if (!recording() && has_audio() && playing()) {
- @if (!recording && studioService.audioControl$.value !== null) { + @if (!recording() && has_audio()) {
- @if (!recording && studioService.audioControl$.value !== null) { + @if (!recording() && has_audio()) { - @if (loading) { + @if (loading()) { = []; - protected loading = false; - protected starting_to_record = false; - protected recording = false; - protected playing = false; + protected loading = signal(false); + protected starting_to_record = signal(false); + protected recording = signal(false); + protected has_audio = signal(false); + protected playing = signal(false); private player: any = null; protected contactLink = environment.packageJson.contact; protected progressMode: ProgressBarMode = "indeterminate"; @@ -103,7 +105,7 @@ export class UploadComponent implements OnInit { this.ssjsService.modelLoaded .pipe(takeUntilDestroyed(this.destroyRef$)) .subscribe((loaded) => { - this.isLoaded = loaded; + this.isLoaded.set(loaded); }); // If the textControl$ changes to a Blob or null, reset the @@ -274,13 +276,16 @@ Please check it to make sure all words are spelled out completely, e.g. write "4 } async startRecording() { - if (this.recording) + if (this.recording()) // Not sure why the button stays clickable, but oh well return; try { - this.starting_to_record = true; + this.starting_to_record.set(true); + console.log("About to startRecording()"); await this.microphoneService.startRecording(); - this.recording = true; + console.log("After startRecording()"); + this.recording.set(true); + console.log(this.recording()); } catch (err: any) { let message; if (err.name === "NotAllowedError") { @@ -292,35 +297,35 @@ Please check it to make sure all words are spelled out completely, e.g. write "4 timeOut: 15000, }); } finally { - this.starting_to_record = false; + this.starting_to_record.set(false); } } pauseRecording() { this.microphoneService.pause(); - this.recording = false; + this.recording.set(false); } resumeRecording() { this.microphoneService.resume(); - this.recording = true; + this.recording.set(true); } playRecording() { - if (!this.playing && this.studioService.audioControl$.value !== null) { + if (!this.playing() && this.studioService.audioControl$.value !== null) { let player = new window.Audio(); this.player = player; player.src = URL.createObjectURL(this.studioService.audioControl$.value); player.onended = () => this.stopPlayback(); player.onerror = () => this.stopPlayback(); player.load(); - this.playing = true; + this.playing.set(true); player.play(); } } stopPlayback() { - this.playing = false; + this.playing.set(false); this.player?.pause(); this.player = null; } @@ -328,10 +333,11 @@ Please check it to make sure all words are spelled out completely, e.g. write "4 deleteRecording() { this.audioFileUpload.nativeElement.value = ""; this.studioService.audioControl$.setValue(null); + this.has_audio.set(false); } async stopRecording() { - this.recording = false; + this.recording.set(false); try { let output = await this.microphoneService.stopRecording(); // possibly check for zero-length output and throw here @@ -340,7 +346,10 @@ Please check it to make sure all words are spelled out completely, e.g. write "4 $localize`Yay!`, { timeOut: 10000 }, ); + console.log(output); this.studioService.audioControl$.setValue(output); + this.has_audio.set(true); + console.log(this.studioService.audioControl$.value); // do any post output steps } catch (err: any) { if (err === "Recorder didn't hear anything") { @@ -500,7 +509,7 @@ Please check it to make sure all words are spelled out completely, e.g. write "4 } // Show progress bar - this.loading = true; + this.loading.set(true); this.progressMode = "query"; // Determine text type for API request let input_type; @@ -582,7 +591,7 @@ Please check it to make sure all words are spelled out completely, e.g. write "4 .subscribe({ next: (progress) => { if (progress.hypseg !== undefined) { - this.loading = false; + this.loading.set(false); this.toastr.toasts.forEach((toast) => { // clean all outstanding success and error toasts on alignment success, // they are no longer relevant, but keep the warnings, they are typically @@ -613,7 +622,7 @@ Please check it to make sure all words are spelled out completely, e.g. write "4 } }, error: (err: Error) => { - this.loading = false; + this.loading.set(false); if (err instanceof HttpErrorResponse) { this.reportRasError(err); } else if (err.message.includes("align")) { @@ -629,6 +638,7 @@ Please check it to make sure all words are spelled out completely, e.g. write "4 const el = event.target as HTMLInputElement; if (!el.files || el.files.length !== 1) { this.studioService.audioControl$.setValue(null); + this.has_audio.set(false); return; } @@ -650,6 +660,7 @@ Please check it to make sure all words are spelled out completely, e.g. write "4 } else { this.studioService.audioControl$.setValue(file); } + this.has_audio.set(true); this.toastr.success( $localize`File ` +