From 297a97ea05bc6acebd69a7de68d124edde0db539 Mon Sep 17 00:00:00 2001 From: DavidMockler Date: Tue, 30 Jul 2024 18:18:03 +0100 Subject: [PATCH] Implemented basic story loader + tested recursive component creation Recursively converting the words/sentences to angular components seems like it will be very slow and so I do not plan to continue that test --- .../endpoint/drStory/publicOrOwnedStoryId.js | 32 +++ .../endpoint/drStory/verifiedCollection.ts | 5 + api/src/routes/drStory.route.ts | 2 + ngapp/package-lock.json | 21 +- ngapp/package.json | 3 +- ngapp/src/app/app-routing.module.ts | 3 + ngapp/src/app/app.module.ts | 2 +- .../src/app/core/services/dr-story.service.ts | 6 +- .../dr-story-builder.component.html | 13 + .../dr-story-builder.component.scss | 229 ++++++++++++++++++ .../dr-story-builder.component.spec.ts | 29 +++ .../dr-story-builder.component.ts | 56 +++++ .../dr-story-viewer.component.html | 13 + .../dr-story-viewer.component.scss | 229 ++++++++++++++++++ .../dr-story-viewer.component.spec.ts | 29 +++ .../dr-story-viewer.component.ts | 175 +++++++++++++ .../digital-reader.component.ts | 1 + .../dr-story-drawer.component.scss | 8 +- .../dr-story-drawer.component.ts | 6 +- 19 files changed, 850 insertions(+), 12 deletions(-) create mode 100644 api/src/endpoint/drStory/publicOrOwnedStoryId.js create mode 100644 ngapp/src/app/dr-story-viewer/dr-story-builder/dr-story-builder.component.html create mode 100644 ngapp/src/app/dr-story-viewer/dr-story-builder/dr-story-builder.component.scss create mode 100644 ngapp/src/app/dr-story-viewer/dr-story-builder/dr-story-builder.component.spec.ts create mode 100644 ngapp/src/app/dr-story-viewer/dr-story-builder/dr-story-builder.component.ts create mode 100644 ngapp/src/app/dr-story-viewer/dr-story-viewer/dr-story-viewer.component.html create mode 100644 ngapp/src/app/dr-story-viewer/dr-story-viewer/dr-story-viewer.component.scss create mode 100644 ngapp/src/app/dr-story-viewer/dr-story-viewer/dr-story-viewer.component.spec.ts create mode 100644 ngapp/src/app/dr-story-viewer/dr-story-viewer/dr-story-viewer.component.ts diff --git a/api/src/endpoint/drStory/publicOrOwnedStoryId.js b/api/src/endpoint/drStory/publicOrOwnedStoryId.js new file mode 100644 index 000000000..946fcb7f8 --- /dev/null +++ b/api/src/endpoint/drStory/publicOrOwnedStoryId.js @@ -0,0 +1,32 @@ +const DigitalReaderStory = require('../../models/drStory'); +const mongoose = require('mongoose'); + +/** + * Set feedback status of story to 'viewed' + * @param {Object} req user: User information; params: story ID + * @param {Object} res + * @param {Object} next + * @return {Promise} Story object + */ +module.exports = async (req, res, next) => { + + function yes() { + res.json(story); + } + function no(status=404, msg='not found') { + res.status(status).json(msg); + } + + if (!req.user) return no(400, 'need to know user'); + if (!req.user._id) return no(400, 'need to know user\'s id'); + + const story = await DigitalReaderStory.findById(new mongoose.mongo.ObjectId(req.params.id)); + if (!story) return no(); + + if (story.public) return yes(); + + // if the story is private - but the current user is its owner, return the story + if (story.owner.toString() === req.user._id) return yes(); + + return no(401, 'not authorised'); +}; diff --git a/api/src/endpoint/drStory/verifiedCollection.ts b/api/src/endpoint/drStory/verifiedCollection.ts index 140e9cc42..ad140b2a4 100644 --- a/api/src/endpoint/drStory/verifiedCollection.ts +++ b/api/src/endpoint/drStory/verifiedCollection.ts @@ -18,16 +18,21 @@ const handler = async (req, res) => { ownerDoc: {$first: '$ownerDocArr'}, collections: 1, thumbnail: 1, + public: 1, }}, {$project: { title: 1, ownerRole: '$ownerDoc.role', collections: 1, thumbnail: 1, + public: 1, }}, {$match: { ownerRole: "ADMIN" }}, + {$match: { + public: true + }}, {$match: { $expr: { $in: [req.params.collectionName, "$collections"] diff --git a/api/src/routes/drStory.route.ts b/api/src/routes/drStory.route.ts index a8cff072c..107b511b7 100644 --- a/api/src/routes/drStory.route.ts +++ b/api/src/routes/drStory.route.ts @@ -27,6 +27,7 @@ let storyRoutes; const allPublic = require("../endpoint/drStory/public"); const verified = require("../endpoint/drStory/verified"); const verifiedCollection = require("../endpoint/drStory/verifiedCollection"); + const publicOrOwnedStoryId = require("../endpoint/drStory/publicOrOwnedStoryId"); /*const author = require("../endpoint/story/author"); const feedbackAudio = require("../endpoint/story/feedbackAudio"); const countGrammarErrors = require("../endpoint/story/countGrammarErrors"); @@ -45,6 +46,7 @@ let storyRoutes; // '/myStudentsStory/:id': myStudentsStory, "/owner/:id": ownerId, "/public": allPublic, + "/publicOrOwned/:id": publicOrOwnedStoryId, "/verified": verified, "/verified/:collectionName": verifiedCollection, /*"/:author": author, diff --git a/ngapp/package-lock.json b/ngapp/package-lock.json index 8ad829d70..f3f50a8fb 100644 --- a/ngapp/package-lock.json +++ b/ngapp/package-lock.json @@ -9,7 +9,8 @@ "version": "1.1.3", "dependencies": { "@phonlab-tcd/gramadoir-ts": "^0.0.55", - "@phonlab-tcd/html2json": "^0.0.2", + "@phonlab-tcd/html2json": "^0.0.4", + "@phonlab-tcd/json2html": "^0.0.2", "@popperjs/core": "^2.9.2", "@tweenjs/tween.js": "^18.6.4", "@types/quill": "^1.3.10", @@ -4478,9 +4479,14 @@ "license": "ISC" }, "node_modules/@phonlab-tcd/html2json": { + "version": "0.0.4", + "resolved": "https://npm.pkg.github.com/download/@phonlab-tcd/html2json/0.0.4/40a0e9ba39ed38aec4cdaaf9ecc9288f67be72f2", + "integrity": "sha512-bqhIVvIXPKSDKFazL4u+1g0DGfVZ+kRXJfh9813jms/6Wv1y0glnKIkhXBdqNr2wKtWxPxrRStdVQ1ghrIMX6A==" + }, + "node_modules/@phonlab-tcd/json2html": { "version": "0.0.2", - "resolved": "https://npm.pkg.github.com/download/@phonlab-tcd/html2json/0.0.2/2a0d9258ec4612497f24dd342cf0b96970524742", - "integrity": "sha512-U0eedMX7dnxgQiDMjAFyV2cUU975c+99HAzuGop1Mgg36ZBNSWCjAQ1tJLqm42JIMgcv9vY4XUsO82L3D8ns3Q==" + "resolved": "https://npm.pkg.github.com/download/@phonlab-tcd/json2html/0.0.2/9a1dff68d69922f172d7f568e3462ef77f25fbe4", + "integrity": "sha512-K/slErjE827QmWXsLBVJGDPN/GCxyEHVMSgF/mE/I9TYIchG98TNdY1btFCN1WN7jvtctRgjxePwVFTeoOHC7w==" }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", @@ -21160,9 +21166,14 @@ "integrity": "sha512-VCcIaQXo3xtlNnC2nTh1Ymteka1SsGv/3ADMAPGK3+S1uGktOmQbDA8J1qmHjl/bZVZhRQmCIpO/vK+W/As/mg==" }, "@phonlab-tcd/html2json": { + "version": "0.0.4", + "resolved": "https://npm.pkg.github.com/download/@phonlab-tcd/html2json/0.0.4/40a0e9ba39ed38aec4cdaaf9ecc9288f67be72f2", + "integrity": "sha512-bqhIVvIXPKSDKFazL4u+1g0DGfVZ+kRXJfh9813jms/6Wv1y0glnKIkhXBdqNr2wKtWxPxrRStdVQ1ghrIMX6A==" + }, + "@phonlab-tcd/json2html": { "version": "0.0.2", - "resolved": "https://npm.pkg.github.com/download/@phonlab-tcd/html2json/0.0.2/2a0d9258ec4612497f24dd342cf0b96970524742", - "integrity": "sha512-U0eedMX7dnxgQiDMjAFyV2cUU975c+99HAzuGop1Mgg36ZBNSWCjAQ1tJLqm42JIMgcv9vY4XUsO82L3D8ns3Q==" + "resolved": "https://npm.pkg.github.com/download/@phonlab-tcd/json2html/0.0.2/9a1dff68d69922f172d7f568e3462ef77f25fbe4", + "integrity": "sha512-K/slErjE827QmWXsLBVJGDPN/GCxyEHVMSgF/mE/I9TYIchG98TNdY1btFCN1WN7jvtctRgjxePwVFTeoOHC7w==" }, "@pkgjs/parseargs": { "version": "0.11.0", diff --git a/ngapp/package.json b/ngapp/package.json index 108131300..3f0c671e8 100644 --- a/ngapp/package.json +++ b/ngapp/package.json @@ -21,7 +21,8 @@ }, "dependencies": { "@phonlab-tcd/gramadoir-ts": "^0.0.55", - "@phonlab-tcd/html2json": "^0.0.2", + "@phonlab-tcd/html2json": "^0.0.4", + "@phonlab-tcd/json2html": "^0.0.2", "@popperjs/core": "^2.9.2", "@tweenjs/tween.js": "^18.6.4", "@types/quill": "^1.3.10", diff --git a/ngapp/src/app/app-routing.module.ts b/ngapp/src/app/app-routing.module.ts index 80004a0a0..fe55b8f76 100644 --- a/ngapp/src/app/app-routing.module.ts +++ b/ngapp/src/app/app-routing.module.ts @@ -9,6 +9,8 @@ import { AuthGuardService } from 'app/core/services/auth-guard.service'; import { RoleGuardService } from 'app/core/services/role-guard.service'; import { NotificationService } from 'app/core/services/notification-service.service'; +//TODO : remove below line and create dr-story-viewer module +import('./dr-story-viewer/dr-story-builder/dr-story-builder.component') const routes: Routes = [ { path: '', redirectTo: 'landing', pathMatch: 'full' }, @@ -24,6 +26,7 @@ const routes: Routes = [ { path: 'profile', loadComponent: () => import('./profile/profile/profile.component').then(m => m.ProfileComponent), canActivate: [AuthGuardService]}, { path: 'messages/:id', loadComponent: () => import('./messages/messages.component').then(m => m.MessagesComponent), canActivate: [AuthGuardService]}, { path: 'stats-dashboard/:id', loadComponent: () => import('./stats-dashboard/stats-dashboard.component').then(m => m.StatsDashboardComponent)}, + { path: 'dr-story-viewer', loadComponent: () => import('./dr-story-viewer/dr-story-viewer/dr-story-viewer.component').then(m => m.DigitalReaderStoryViewerComponent)}, { path: 'student', data: { expectedRoles: ['STUDENT'] }, diff --git a/ngapp/src/app/app.module.ts b/ngapp/src/app/app.module.ts index ae77b12ad..abfa1d275 100644 --- a/ngapp/src/app/app.module.ts +++ b/ngapp/src/app/app.module.ts @@ -20,7 +20,7 @@ import { NavBarModule } from './nav-bar/nav-bar.module'; @NgModule({ declarations: [ - AppComponent, + AppComponent ], imports: [ BrowserModule, diff --git a/ngapp/src/app/core/services/dr-story.service.ts b/ngapp/src/app/core/services/dr-story.service.ts index 2b3a2708e..9d85f4cf1 100644 --- a/ngapp/src/app/core/services/dr-story.service.ts +++ b/ngapp/src/app/core/services/dr-story.service.ts @@ -111,7 +111,7 @@ export class DigitalReaderStoryService { this.http.post(this.baseUrl + 'digitalReader/docx2html', formData) ).catch((err: HttpErrorResponse) => { alert(err.error) - alert(err) + //alert(err) //throw new Error() return null; }); @@ -204,6 +204,10 @@ export class DigitalReaderStoryService { return this.http.get(this.baseUrl + 'drStory/public'); } + getPublicDRStoryById(id: string): Observable { + return this.http.get(this.baseUrl + 'drStory/publicOrOwned/' + id); + } + /* createDRStory(title: string, date: Date, dialects: Array, htmlText: string, author: string) { const drstoryObj = { diff --git a/ngapp/src/app/dr-story-viewer/dr-story-builder/dr-story-builder.component.html b/ngapp/src/app/dr-story-viewer/dr-story-builder/dr-story-builder.component.html new file mode 100644 index 000000000..c824ce415 --- /dev/null +++ b/ngapp/src/app/dr-story-viewer/dr-story-builder/dr-story-builder.component.html @@ -0,0 +1,13 @@ + + + + {{ content.nodeName }} + + + + \ No newline at end of file diff --git a/ngapp/src/app/dr-story-viewer/dr-story-builder/dr-story-builder.component.scss b/ngapp/src/app/dr-story-viewer/dr-story-builder/dr-story-builder.component.scss new file mode 100644 index 000000000..a6dd0e56e --- /dev/null +++ b/ngapp/src/app/dr-story-viewer/dr-story-builder/dr-story-builder.component.scss @@ -0,0 +1,229 @@ +.container { + max-width: 70%; + padding-bottom: 20px; + animation-duration: 1s; + animation-name: fadeIn; +} + +.playback { + margin: 8px; + padding: 10px; + font-weight: bold; +} + +.synth-item { + margin-bottom: 4px; +} + +.speed-container { + border-radius: 0px; + border-width: 1px; + border-color: black; + max-width: fit-content; + color: black; + font-weight: 500; + display: inline; + justify-content: center; +} + +.button.timer-button { + margin:4px; border-radius: 4px; border-width: 1px; +} + +.timer-button.on { + background-color: #6eff81; +} + +.timer-button.on:hover { + background-color: #6eff81; +} + +.timer-button.off { + background-color: rgba(255, 60, 60, 0.76); +} + +.timer-button.off:hover { + background-color: rgba(255, 60, 60, 0.76); +} + +.button.timer-button.unavailable { + background-color: rgba(110, 110, 110, 0.76); +} + +.timer-button.unavailable:hover { + background-color: rgba(110, 110, 110, 0.76); + box-shadow: 0px 15px 20px rgba(71, 71, 71, 0.2); +} + +.victory { + font-family: Verdana, Geneva, Tahoma, sans-serif; + color: darkolivegreen; +} + +.buttons-and-templates { + display: flex; + flex-wrap: wrap; + flex-direction: row; +} + +.hiddenWordButton { + margin-top: 4px; + border-radius: 4px; + border-style: solid; + padding: 10px; +} + +.hiddenWordButton.guessed { + border-color: rgba($color: #008f13, $alpha: 1.0); + background-color: rgba($color: rgba(131, 255, 131, 0.459), $alpha: 1.0); +} + +.hiddenWordButton.pressed { + border-color: rgba($color: #8f6200, $alpha: 1.0); + background-color: rgba($color: rgba(255, 199, 46, 0.671), $alpha: 1.0); +} + +.hiddenWordButton.unguessed { + border-color: rgba($color: #8f0000, $alpha: 1.0); + background-color: rgba($color: rgba(255, 131, 131, 0.459), $alpha: 1.0); +} + +.button-text { + margin: 0px; +} + +.unguessed-class { + display: grid; + justify-content: center; + position: relative; +} + +.unguessed-class .hint { + visibility: hidden; + min-width: 200px; + background-color: rgba(0, 0, 0, 0.5); + color: #fff; + text-align: center; + border-radius: 6px; + padding: 5px 0; + position: absolute; + top: -35px; + } + +.unguessed-class:hover .hint{ + visibility: visible; +} + +.textarea { + width: 90%; + resize: none; + border-radius: 1px; + border-style: solid; + border-radius: 4px; + padding: 4px; +} + +.victory-button { + border-width: 2px; + font-size: 14px; + background-color: rgba(112, 168, 127, 0.1); + border-color: rgb(112, 168, 127); + border-style: solid; +} + +.speed-button { + border-radius: 0px; + border-width: 1px; +} + +.button { + border-color: rgba(0, 0, 0, 0.1); + border-radius: 8px; + border-width: 1px; + letter-spacing: 1px; + background-color: rgba(126, 118, 118, 0.1); + margin: 4px; + transition: all 0.3s ease 0s; + box-shadow: 0px 8px 15px rgba(0, 0, 0, 0.1); +} + +.button:hover { + background-color: var(--scealai-green); + color: #fff; + transform: translateY(-2px); +} + +.playback-header { + margin: 2px; + color: black; + font-weight: 500; +} + +.speed-button:hover { + cursor: pointer; +} + +.synth-container { + display: flex; + flex-wrap: wrap; + justify-content: space-evenly; + padding: 6px; +} + +.recordingIcon { + margin-left: 0px; +} + +.wrongWordBank { + padding: 12px; + display: flex; + flex-direction: row; + justify-content: center; + gap: 1rem; + flex-wrap: wrap; +} + +.wordBankItem { + border-color: rgba(0, 0, 0, 0.1); + border-radius: 8px; + border-width: 1px; + letter-spacing: 1px; + background-color: rgba(126, 118, 118, 0.1); + padding: 10px; + font-weight: bold; +} + + +@keyframes fadeIn { + from { + opacity: 0%; + transform: translateY(100px) scale(0.9); + } + + to { + opacity: 100%; + transform: translateY(0px) scale(1); + } +} + +@keyframes fadeOut { + from { + opacity: 100%; + transform: translateY(0px) scale(1); + } + + to { + opacity: 0%; + transform: translateY(100px) scale(0.9); + } +} + +@keyframes fadeInButton { + from { + opacity: 0%; + } + + to { + opacity: 100%; + } +} \ No newline at end of file diff --git a/ngapp/src/app/dr-story-viewer/dr-story-builder/dr-story-builder.component.spec.ts b/ngapp/src/app/dr-story-viewer/dr-story-builder/dr-story-builder.component.spec.ts new file mode 100644 index 000000000..c2c901925 --- /dev/null +++ b/ngapp/src/app/dr-story-viewer/dr-story-builder/dr-story-builder.component.spec.ts @@ -0,0 +1,29 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { MatDialog } from '@angular/material/dialog'; +import { DigitalReaderStoryBuilderComponent } from './dr-story-builder.component'; + +describe('DigitalReaderStoryBuilderComponent', () => { + let component: DigitalReaderStoryBuilderComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ DigitalReaderStoryBuilderComponent ], + imports: [RouterTestingModule, HttpClientTestingModule], + providers: [ + { provide: MatDialog, useValue: {} } + ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(DigitalReaderStoryBuilderComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ngapp/src/app/dr-story-viewer/dr-story-builder/dr-story-builder.component.ts b/ngapp/src/app/dr-story-viewer/dr-story-builder/dr-story-builder.component.ts new file mode 100644 index 000000000..2a9fec154 --- /dev/null +++ b/ngapp/src/app/dr-story-viewer/dr-story-builder/dr-story-builder.component.ts @@ -0,0 +1,56 @@ +import { Component, OnInit, ViewChild, ElementRef, Input } from "@angular/core"; +import { SynthItem } from "app/core/models/synth-item"; +import { SynthesisService, Voice } from "app/core/services/synthesis.service"; +import { TranslationService } from "app/core/services/translation.service"; +import { SynthesisPlayerComponent } from "app/student/synthesis-player/synthesis-player.component"; +import { ActivatedRoute, Router } from "@angular/router"; +import { AuthenticationService } from "app/core/services/authentication.service"; +import { DomSanitizer } from "@angular/platform-browser"; +/*import { ClassroomService } from "app/core/services/classroom.service"; +import { MessageService } from "app/core/services/message.service"; +import { RecordAudioService } from "app/core/services/record-audio.service"; +import { DigitalReaderStoryService } from "app/core/services/dr-story.service"; +import { SynthVoiceSelectComponent } from "app/synth-voice-select/synth-voice-select.component"; +import { firstValueFrom } from "rxjs"; +import { MatDialog, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; +import { BasicDialogComponent } from '../dialogs/basic-dialog/basic-dialog.component'; +import { Message } from "app/core/models/message";*/ +import { CommonModule } from "@angular/common"; +/*import { SynthItemModule } from "app/synth-item/synth-item.module"; +import { SynthVoiceSelectModule } from "app/synth-voice-select/synth-voice-select.module"; +import { EngagementService } from "app/core/services/engagement.service"; +import { EventType } from "app/core/models/event";*/ + +import { constructHTML } from '@phonlab-tcd/json2html'; + +@Component({ + standalone: true, + imports: [CommonModule], + selector: "app-dr-story-builder", + templateUrl: "./dr-story-builder.component.html", + styleUrls: ["./dr-story-builder.component.scss"], +}) +export class DigitalReaderStoryBuilderComponent implements OnInit { + + @Input() type='' + @Input() content:Node + + constructor( + + private auth: AuthenticationService, + private synth: SynthesisService, + private router: Router, + public activatedRoute: ActivatedRoute, + public ts: TranslationService, + protected sanitizer: DomSanitizer, + ) { + + } + + async ngOnInit() { + //console.log(this.content.childNodes) + //console.log('gets to here!') + console.log(this.content.nodeName) + } + +} diff --git a/ngapp/src/app/dr-story-viewer/dr-story-viewer/dr-story-viewer.component.html b/ngapp/src/app/dr-story-viewer/dr-story-viewer/dr-story-viewer.component.html new file mode 100644 index 000000000..f324e4b3b --- /dev/null +++ b/ngapp/src/app/dr-story-viewer/dr-story-viewer/dr-story-viewer.component.html @@ -0,0 +1,13 @@ + +
+

Test page!

+
+ + + + \ No newline at end of file diff --git a/ngapp/src/app/dr-story-viewer/dr-story-viewer/dr-story-viewer.component.scss b/ngapp/src/app/dr-story-viewer/dr-story-viewer/dr-story-viewer.component.scss new file mode 100644 index 000000000..a6dd0e56e --- /dev/null +++ b/ngapp/src/app/dr-story-viewer/dr-story-viewer/dr-story-viewer.component.scss @@ -0,0 +1,229 @@ +.container { + max-width: 70%; + padding-bottom: 20px; + animation-duration: 1s; + animation-name: fadeIn; +} + +.playback { + margin: 8px; + padding: 10px; + font-weight: bold; +} + +.synth-item { + margin-bottom: 4px; +} + +.speed-container { + border-radius: 0px; + border-width: 1px; + border-color: black; + max-width: fit-content; + color: black; + font-weight: 500; + display: inline; + justify-content: center; +} + +.button.timer-button { + margin:4px; border-radius: 4px; border-width: 1px; +} + +.timer-button.on { + background-color: #6eff81; +} + +.timer-button.on:hover { + background-color: #6eff81; +} + +.timer-button.off { + background-color: rgba(255, 60, 60, 0.76); +} + +.timer-button.off:hover { + background-color: rgba(255, 60, 60, 0.76); +} + +.button.timer-button.unavailable { + background-color: rgba(110, 110, 110, 0.76); +} + +.timer-button.unavailable:hover { + background-color: rgba(110, 110, 110, 0.76); + box-shadow: 0px 15px 20px rgba(71, 71, 71, 0.2); +} + +.victory { + font-family: Verdana, Geneva, Tahoma, sans-serif; + color: darkolivegreen; +} + +.buttons-and-templates { + display: flex; + flex-wrap: wrap; + flex-direction: row; +} + +.hiddenWordButton { + margin-top: 4px; + border-radius: 4px; + border-style: solid; + padding: 10px; +} + +.hiddenWordButton.guessed { + border-color: rgba($color: #008f13, $alpha: 1.0); + background-color: rgba($color: rgba(131, 255, 131, 0.459), $alpha: 1.0); +} + +.hiddenWordButton.pressed { + border-color: rgba($color: #8f6200, $alpha: 1.0); + background-color: rgba($color: rgba(255, 199, 46, 0.671), $alpha: 1.0); +} + +.hiddenWordButton.unguessed { + border-color: rgba($color: #8f0000, $alpha: 1.0); + background-color: rgba($color: rgba(255, 131, 131, 0.459), $alpha: 1.0); +} + +.button-text { + margin: 0px; +} + +.unguessed-class { + display: grid; + justify-content: center; + position: relative; +} + +.unguessed-class .hint { + visibility: hidden; + min-width: 200px; + background-color: rgba(0, 0, 0, 0.5); + color: #fff; + text-align: center; + border-radius: 6px; + padding: 5px 0; + position: absolute; + top: -35px; + } + +.unguessed-class:hover .hint{ + visibility: visible; +} + +.textarea { + width: 90%; + resize: none; + border-radius: 1px; + border-style: solid; + border-radius: 4px; + padding: 4px; +} + +.victory-button { + border-width: 2px; + font-size: 14px; + background-color: rgba(112, 168, 127, 0.1); + border-color: rgb(112, 168, 127); + border-style: solid; +} + +.speed-button { + border-radius: 0px; + border-width: 1px; +} + +.button { + border-color: rgba(0, 0, 0, 0.1); + border-radius: 8px; + border-width: 1px; + letter-spacing: 1px; + background-color: rgba(126, 118, 118, 0.1); + margin: 4px; + transition: all 0.3s ease 0s; + box-shadow: 0px 8px 15px rgba(0, 0, 0, 0.1); +} + +.button:hover { + background-color: var(--scealai-green); + color: #fff; + transform: translateY(-2px); +} + +.playback-header { + margin: 2px; + color: black; + font-weight: 500; +} + +.speed-button:hover { + cursor: pointer; +} + +.synth-container { + display: flex; + flex-wrap: wrap; + justify-content: space-evenly; + padding: 6px; +} + +.recordingIcon { + margin-left: 0px; +} + +.wrongWordBank { + padding: 12px; + display: flex; + flex-direction: row; + justify-content: center; + gap: 1rem; + flex-wrap: wrap; +} + +.wordBankItem { + border-color: rgba(0, 0, 0, 0.1); + border-radius: 8px; + border-width: 1px; + letter-spacing: 1px; + background-color: rgba(126, 118, 118, 0.1); + padding: 10px; + font-weight: bold; +} + + +@keyframes fadeIn { + from { + opacity: 0%; + transform: translateY(100px) scale(0.9); + } + + to { + opacity: 100%; + transform: translateY(0px) scale(1); + } +} + +@keyframes fadeOut { + from { + opacity: 100%; + transform: translateY(0px) scale(1); + } + + to { + opacity: 0%; + transform: translateY(100px) scale(0.9); + } +} + +@keyframes fadeInButton { + from { + opacity: 0%; + } + + to { + opacity: 100%; + } +} \ No newline at end of file diff --git a/ngapp/src/app/dr-story-viewer/dr-story-viewer/dr-story-viewer.component.spec.ts b/ngapp/src/app/dr-story-viewer/dr-story-viewer/dr-story-viewer.component.spec.ts new file mode 100644 index 000000000..4f496aa41 --- /dev/null +++ b/ngapp/src/app/dr-story-viewer/dr-story-viewer/dr-story-viewer.component.spec.ts @@ -0,0 +1,29 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { MatDialog } from '@angular/material/dialog'; +import { DigitalReaderStoryViewerComponent } from './dr-story-viewer.component'; + +describe('DigitalReaderStoryViewerComponent', () => { + let component: DigitalReaderStoryViewerComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ DigitalReaderStoryViewerComponent ], + imports: [RouterTestingModule, HttpClientTestingModule], + providers: [ + { provide: MatDialog, useValue: {} } + ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(DigitalReaderStoryViewerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ngapp/src/app/dr-story-viewer/dr-story-viewer/dr-story-viewer.component.ts b/ngapp/src/app/dr-story-viewer/dr-story-viewer/dr-story-viewer.component.ts new file mode 100644 index 000000000..53011f380 --- /dev/null +++ b/ngapp/src/app/dr-story-viewer/dr-story-viewer/dr-story-viewer.component.ts @@ -0,0 +1,175 @@ +import { Component, OnInit, ViewChild, ElementRef } from "@angular/core"; +import { SynthItem } from "app/core/models/synth-item"; +import { SynthesisService, Voice } from "app/core/services/synthesis.service"; +import { TranslationService } from "app/core/services/translation.service"; +import { SynthesisPlayerComponent } from "app/student/synthesis-player/synthesis-player.component"; +import { ActivatedRoute, Router } from "@angular/router"; +import { AuthenticationService } from "app/core/services/authentication.service"; +import { ClassroomService } from "app/core/services/classroom.service"; +import { MessageService } from "app/core/services/message.service"; +import { RecordAudioService } from "app/core/services/record-audio.service"; +import { DigitalReaderStoryService } from "app/core/services/dr-story.service"; +import { SynthVoiceSelectComponent } from "app/synth-voice-select/synth-voice-select.component"; +import { DomSanitizer } from "@angular/platform-browser"; +import { firstValueFrom } from "rxjs"; +import { MatDialog, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; +import { BasicDialogComponent } from 'app/dialogs/basic-dialog/basic-dialog.component'; +import { Message } from "app/core/models/message"; +import { CommonModule } from "@angular/common"; +import { SynthItemModule } from "app/synth-item/synth-item.module"; +import { SynthVoiceSelectModule } from "app/synth-voice-select/synth-voice-select.module"; +import { EngagementService } from "app/core/services/engagement.service"; +import { EventType } from "app/core/models/event"; + +import { constructHTML } from '@phonlab-tcd/json2html'; +import { DigitalReaderStoryBuilderComponent } from "../dr-story-builder/dr-story-builder.component"; + +@Component({ + standalone: true, + imports: [CommonModule, SynthItemModule, SynthVoiceSelectModule, MatDialogModule, DigitalReaderStoryBuilderComponent], + selector: "app-dr-story-viewer", + templateUrl: "./dr-story-viewer.component.html", + styleUrls: ["./dr-story-viewer.component.scss"], +}) +export class DigitalReaderStoryViewerComponent implements OnInit { + // dictogloss variables + generatedFromMessages: boolean; + texts: string = ""; + wrongWordsDiv: string = ""; + words: string[] = []; + shownWords: string[] = []; + wrongWords: string[] = []; + wordsPunc: string[] = []; + wordsPuncLower: string[] = []; + sentences: string[] = []; + hasText: boolean = false; + guess: string = ""; + regex: any = /[^a-zA-Z0-9áÁóÓúÚíÍéÉ:]+/; + regexg: any = /([^a-zA-Z0-9áÁóÓúÚíÍéÉ:]+)/g; + gameInProgress: boolean = false; + showInputBox: boolean = true; + allGuessed: boolean = false; + guessCheck: boolean = false; + wrongCount: number = 0; + rightCount: number = 0; + + // game options + playWithTimer = false; + totalTime: number = 0; + interval: any; + isRecording: boolean = false; + dialogRef: MatDialogRef; + + // synthesis variables + synthesisPlayer: SynthesisPlayerComponent; + playbackSpeed: number = 1; //Shoud range from 0.5x to 2x speed incrementing in 0.5. + @ViewChild("voiceSelect") voiceSelect: ElementRef; + selectedVoice: Voice | undefined; + synthItem: SynthItem; + errorText: boolean = false; + synthItems: SynthItem[] = []; + + storyId: string = ''; + storyObj: Object = {}; + story: Document; + + // user variables + studentId: string = ""; + teacherId: string = ""; + + constructor( + private messageService: MessageService, + private classroomService: ClassroomService, + private auth: AuthenticationService, + private synth: SynthesisService, + private drStoryService: DigitalReaderStoryService, + private router: Router, + public activatedRoute: ActivatedRoute, + public ts: TranslationService, + protected sanitizer: DomSanitizer, + private recordAudioService: RecordAudioService, + private dialog: MatDialog, + private engagement: EngagementService + ) { + // see if dictogloss is generated from messages + //try { + const storyId = this.router.getCurrentNavigation()?.initialUrl.queryParams['storyId'] + if (storyId) { + this.storyId = storyId + console.log(storyId) + console.log(this.storyId) + } else { + console.log('No story id provided') + this.goToDRStoryLibrary('Please select a story from the library') //TODO : Add translation to ts + } + } + + /** + * Get user details -> id, role + * Refresh synthesis voice settings + * Check if dictogloss is sent from messages + */ + async ngOnInit() { + let storyObj = {} + try { + storyObj = await firstValueFrom(this.drStoryService.getPublicDRStoryById(this.storyId)) + console.log(storyObj) + } catch (err) { + console.log(err) + this.goToDRStoryLibrary('Please select a story from the library') //TODO : Add translation to ts + return + } + + //console.log(Object.keys(storyObj).length) + if (Object.keys(storyObj).length!==0) + this.storyObj = storyObj + else { + this.goToDRStoryLibrary('Invalid story selection') //TODO : Add translation to ts + return + } + + this.story = constructHTML(this.storyObj['story']) + console.log(this.story) + + /*const userDetails = this.auth.getUserDetails(); + if (!userDetails) return; + + if (userDetails.role === "TEACHER") { + this.teacherId = userDetails._id; + } + if (userDetails.role === "STUDENT") { + this.studentId = userDetails._id; + const classroom_cache = localStorage.getItem("classroom"); + const studentClassroom = classroom_cache ? JSON.parse(classroom_cache) : await firstValueFrom( this.classroomService.getClassroomOfStudent(this.studentId) ); + if (studentClassroom) this.teacherId = studentClassroom.teacherId; + } + this.refreshVoice();*/ + + // create a dictogloss from the text sent by teacher + //if (this.texts) { + //this.loadDictogloss(); + //} + } + + goToDRStoryLibrary(alertMessage?:string) { + this.router.navigateByUrl('dr-library') + if (alertMessage) alert(alertMessage) + } + + /** + * Refresh the synthetic voice: deletes old audio urls and creates new array + * @param voice Synthesis voice option + * @returns + */ + refreshVoice(voice: Voice | undefined = undefined) { + if (voice) this.selectedVoice = voice; + this.synthItems.forEach((s: SynthItem) => { + s.audioUrl = undefined; + s.dispose(); + }); + this.synthItems = []; + console.log('refresh synth') + //this.collateSynths(); + } + +} diff --git a/ngapp/src/app/nav-bar/digital-reader/digital-reader.component.ts b/ngapp/src/app/nav-bar/digital-reader/digital-reader.component.ts index 33aee66d1..d219bbee2 100644 --- a/ngapp/src/app/nav-bar/digital-reader/digital-reader.component.ts +++ b/ngapp/src/app/nav-bar/digital-reader/digital-reader.component.ts @@ -187,6 +187,7 @@ export class DigitalReaderComponent implements OnInit { } if (this.convertedHTMLDoc) { + //console.log(this.convertedHTMLDoc) const story = constructJSON(this.convertedHTMLDoc.body) console.log(story) diff --git a/ngapp/src/app/nav-bar/dr-story-drawer/dr-story-drawer.component.scss b/ngapp/src/app/nav-bar/dr-story-drawer/dr-story-drawer.component.scss index 00306a4a5..0e9ac0174 100644 --- a/ngapp/src/app/nav-bar/dr-story-drawer/dr-story-drawer.component.scss +++ b/ngapp/src/app/nav-bar/dr-story-drawer/dr-story-drawer.component.scss @@ -39,13 +39,15 @@ width: 250px; //height: 270px; height: 85%; - position:relative + position:relative; + + background-color: #eee; + border-radius: 25px; } .contentSection:hover { cursor: pointer; - background-color: #eee; - border-radius: 25px; + background-color: var(--scealai-cream); } .titleContainer { diff --git a/ngapp/src/app/nav-bar/dr-story-drawer/dr-story-drawer.component.ts b/ngapp/src/app/nav-bar/dr-story-drawer/dr-story-drawer.component.ts index 80e0b5d10..dbd4b8f94 100644 --- a/ngapp/src/app/nav-bar/dr-story-drawer/dr-story-drawer.component.ts +++ b/ngapp/src/app/nav-bar/dr-story-drawer/dr-story-drawer.component.ts @@ -10,6 +10,7 @@ import { EventType } from "../../core/models/event"; import { MatDialog, MatDialogRef } from "@angular/material/dialog"; import { BasicDialogComponent } from "../../dialogs/basic-dialog/basic-dialog.component"; import { DigitalReaderStory } from "app/core/models/drStory"; +import { Router } from "@angular/router"; //import { UserService } from "app/core/services/user.service"; @@ -39,6 +40,7 @@ export class DigitalReaderStoryDrawerComponent implements OnInit { public ts: TranslationService, private auth: AuthenticationService, private drStoryService: DigitalReaderStoryService, + private router: Router, //private userService: UserService, private engagement: EngagementService, private recordingService: RecordingService, @@ -75,7 +77,9 @@ export class DigitalReaderStoryDrawerComponent implements OnInit { * @param story Selected story from HTML */ //setStory(story: DigitalReaderStory){} - openStory(story: DigitalReaderStory){} + openStory(story: DigitalReaderStory){ + this.router.navigateByUrl('dr-story-viewer?storyId=' + story._id) + } /*setStory(story: Story) { if (story.htmlText == null) { story.htmlText = story.text;