diff --git a/api/src/endpoint/digitalReader/dr-ify_html.ts b/api/src/endpoint/digitalReader/dr-ify_html.ts new file mode 100644 index 000000000..4b438ce10 --- /dev/null +++ b/api/src/endpoint/digitalReader/dr-ify_html.ts @@ -0,0 +1,29 @@ +import {API400Error} from '../../utils/APIError'; +//import { segment } from '../../utils/segment'; +//const { spawn } = require('node:child_process'); +import { spawn } from 'node:child_process' + +// tokenise story text into sentences +export default async function convertHtmlToDigitalReaderDoc (req, res) { + if (!req.body.text) throw new API400Error('Must include text parameter in the request body.'); + //const tokens = segment(req.body.text); + // We want to split any tokens containing newline symbols into separate tokens. + // @ts-ignore -- for some reason it thinks acc has never[] type... but it should be string[] + //const tokensWithoutNewlines = tokens.reduce((acc, curr) => acc.concat(curr.split("\n")), []); + //res.json(tokensWithoutNewlines); + + // test.py not yet added to GitHub + /*const py = spawn('python', ['./bin/python/test.py']); + + let result = '' + py.stdout.on('data', (data) => { + result += data + }); + py.on('close', () => { + //add try catch + res.json(JSON.parse(result)) + }); + py.on('error', (err) => { + res.json(err) + });*/ +} diff --git a/api/src/endpoint/digitalReader/unzip.ts b/api/src/endpoint/digitalReader/unzip.ts new file mode 100644 index 000000000..5f150b504 --- /dev/null +++ b/api/src/endpoint/digitalReader/unzip.ts @@ -0,0 +1,23 @@ +import {API400Error} from '../../utils/APIError'; +//import { segment } from '../../utils/segment'; +//const { spawn } = require('node:child_process'); +import { spawn } from 'node:child_process' + +// tokenise story text into sentences +export default async function unzipHtml (req, res) { + if (!req.body.file) throw new API400Error('Must include file parameter in the request body.'); + + const unzip = spawn('bash', ['./bin/unzip/unzipHtml.bash']); + + let result = '' + unzip.stdout.on('data', (data) => { + result += data + }); + unzip.on('close', () => { + //add try catch + res.json(JSON.parse(result)) + }); + unzip.on('error', (err) => { + res.json(err) + }); +} diff --git a/api/src/routes/digitalReader.route.ts b/api/src/routes/digitalReader.route.ts new file mode 100644 index 000000000..7cf90e2e9 --- /dev/null +++ b/api/src/routes/digitalReader.route.ts @@ -0,0 +1,8 @@ +const makeEndpoints = require('../utils/makeEndpoints'); + +export = makeEndpoints({ + post: { + '/convert': require('../endpoint/digitalReader/dr-ify_html').default, //convert a html document to a digital-reader story + //'/unzip': require('../endpoint/digitalReader/unzip') //currently there is a bug with this code + }, + }); \ No newline at end of file diff --git a/api/src/server.ts b/api/src/server.ts index 0a917fb44..5dbe97d57 100644 --- a/api/src/server.ts +++ b/api/src/server.ts @@ -38,6 +38,7 @@ const gramadoirRoute = require('./routes/gramadoir.route'); const synthesisRoute = require('./routes/synthesis.route'); const nlpRoute = require('./routes/nlp.route'); import feedbackCommentRoute from './routes/feedbackComment.route'; +const digitalReaderRoute = require("./routes/digitalReader.route"); mongoose.Promise = global.Promise; mongoose.set('strictQuery', false) @@ -122,6 +123,7 @@ app.use('/gramadoir', expressQueue({activeLimit: 40, queuedLimit: -1}), gramadoi app.use('/recordings', recordingRoute); app.use('/nlp', nlpRoute); app.use('/feedbackComment', feedbackCommentRoute); +app.use('/digitalReader', digitalReaderRoute); app.use('/proxy', expressQueue({activeLimit: 2, queuedLimit: -1}), async (req,res,next)=>{ function allowUrl(url) { diff --git a/ngapp/src/app/core/services/dr-story.service.spec.ts b/ngapp/src/app/core/services/dr-story.service.spec.ts new file mode 100644 index 000000000..4150c0e87 --- /dev/null +++ b/ngapp/src/app/core/services/dr-story.service.spec.ts @@ -0,0 +1,15 @@ +import { TestBed } from '@angular/core/testing'; +import { DigitalReaderStoryService } from './dr-story.service'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; + +describe('DigitalReaderStoryService', () => { + beforeEach(() => TestBed.configureTestingModule({ + imports: [RouterTestingModule, HttpClientTestingModule] + })); + + it('should be created', () => { + const service: DigitalReaderStoryService = TestBed.inject(DigitalReaderStoryService); + expect(service).toBeTruthy(); + }); +}); diff --git a/ngapp/src/app/core/services/dr-story.service.ts b/ngapp/src/app/core/services/dr-story.service.ts new file mode 100644 index 000000000..a4d7b4126 --- /dev/null +++ b/ngapp/src/app/core/services/dr-story.service.ts @@ -0,0 +1,255 @@ +import { Injectable } from '@angular/core'; +//import { DigitalReaderStory } from 'app/core/models/dr-story'; +import { HttpClient } from '@angular/common/http'; +import { Router } from '@angular/router'; +import { AuthenticationService } from 'app/core/services/authentication.service'; +import { Observable } from 'rxjs'; +import { EngagementService } from 'app/core/services/engagement.service'; +import { EventType } from 'app/core/models/event'; +import { TranslationService } from 'app/core/services/translation.service'; +import config from 'abairconfig'; +import { firstValueFrom, tap } from 'rxjs'; + + +@Injectable({ + providedIn: 'root' +}) +export class DigitalReaderStoryService { + + constructor( + private http: HttpClient, + private router: Router, + private auth: AuthenticationService, + private engagement: EngagementService, + private ts: TranslationService, + ) { } + + baseUrl: string = config.baseurl //+ 'nlp/'; + segmentableTags: string = 'p, h1, h2, h3, h4, h5, h6, li, title, th, td' + + async tokenizeSentence(input: string) { + const sentences = await firstValueFrom( + this.http.post>(this.baseUrl + 'nlp/sentenceTokenize', {text: input}) + ) + return sentences + } + + /*async testChildProcess() { + const out = await firstValueFrom( + this.http.post<{id: string}>(this.baseUrl + 'digitalReader/convert', {text: 'test!\nrud eile\nrud eile'}) + ) + return out + }*/ + + //may need to parse from a string rather than a document + extractText(inputHtml: Document) { + const textTags = inputHtml.querySelectorAll(this.segmentableTags) + + const textChunks = [] + for (let i = 0; i < textTags.length; i++) { + textChunks.push(textTags[i].textContent) + } + + return textChunks + } + + async segmentText(document: Document) { + + const chunkedText = this.extractText(document) + + const segmentedSentences = [] + for (let chunk of chunkedText) { + const segmentedChunkSentences = await this.tokenizeSentence(chunk) + //console.log('test') + //console.log(segmentedChunkSentences) + + for (let i = 0; i < segmentedChunkSentences.length; i++) { + const segmentedChunkSentence = segmentedChunkSentences[i]; + segmentedSentences.push(segmentedChunkSentence) + } + } + + return segmentedSentences + } + + async testChildProcess() { + const out = await firstValueFrom( + this.http.post(this.baseUrl + 'digitalReader/unzip', {body: 'test!\nrud eile\nrud eile'}) + ) + return out + } + + /* + createDRStory(title: string, date: Date, dialects: Array, htmlText: string, author: string) { + const drstoryObj = { + title: title, + dialects: dialects, + //text: text, + htmlText: htmlText, + author: author, + //createdWithPrompts: createdWithPrompts, + //activeRecording: null + }; + console.log(drstoryObj); + this.engagement.addEvent(EventType['CREATE-DR-STORY'], {storyObject: drstoryObj}); + return this.http.post<{id: string}>(this.baseUrl + 'create', drstoryObj); + } + + getDRStoriesFor(author : string): Observable { + return this.http.get(this.baseUrl + author); + } + + getDRStoriesByOwner(owner: string) : Observable { + return this.http.get(this.baseUrl + 'owner/' + owner); + } + + getDRStory(id: string) : Observable { + return this.http.get(this.baseUrl + 'withId/' + id); + } + + getDRStoriesByDate(studentId:string, startDate:string, endDate:string) : Observable { + return this.http.post(this.baseUrl + "getStoriesByDate/" + studentId, {startDate:startDate, endDate:endDate}); + } + + getDRStoriesForLoggedInUser(): Observable { + const userDetails = this.auth.getUserDetails(); + if(!userDetails) { + return new Observable(subscriber=>{ + subscriber.next([]); + subscriber.complete(); + }); + } + return this.getDRStoriesByOwner(userDetails._id); + } + + saveStory(title: string, date: Date, dialect: string, text: string, author: string, createdWithPrompts: boolean) { + const storyObj = { + title: title, + dialect: dialect, + text: text, + htmlText: text, + author: author, + createdWithPrompts: createdWithPrompts, + activeRecording: null + }; + console.log(storyObj); + this.engagement.addEvent(EventType['CREATE-STORY'], {storyObject: storyObj}); + return this.http.post<{id: string}>(this.baseUrl + 'create', storyObj); + } + + getStoriesFor(author : string): Observable { + return this.http.get(this.baseUrl + author); + } + + getStoriesByOwner(owner: string) : Observable { + return this.http.get(this.baseUrl + 'owner/' + owner); + } + + getStory(id: string) : Observable { + return this.http.get(this.baseUrl + 'withId/' + id); + } + + getStoriesByDate(studentId:string, startDate:string, endDate:string) : Observable { + return this.http.post(this.baseUrl + "getStoriesByDate/" + studentId, {startDate:startDate, endDate:endDate}); + } + + getStoriesForLoggedInUser(): Observable { + const userDetails = this.auth.getUserDetails(); + if(!userDetails) { + return new Observable(subscriber=>{ + subscriber.next([]); + subscriber.complete(); + }); + } + return this.getStoriesByOwner(userDetails._id); + } + + updateStoryTitleAndDialect(story: Story, title:string, dialect:any): Observable { + let updatedStory = story; + if (title) updatedStory.title = title; + + if (dialect == this.ts.l.connacht) updatedStory.dialect = 'connemara'; + if (dialect == this.ts.l.munster) updatedStory.dialect = 'kerry'; + if (dialect == this.ts.l.ulster) updatedStory.dialect = 'donegal'; + + console.log(updatedStory); + + return this.http.post(this.baseUrl + 'update/' + story._id, updatedStory); + } + + updateTitle(storyId: string, title:string): Observable { + return this.http.post(this.baseUrl + 'updateTitle/' + storyId, {title}); + } + + getStoriesForClassroom(owner: string, date = 'empty'): Observable { + return this.http.get(this.baseUrl + "getStoriesForClassroom/" + owner + "/" + date); + } + + getNumberOfStories(owner: string, date = 'empty'): Observable { + return this.http.get(this.baseUrl + "getNumberOfStories/" + owner + "/" + date); + } + + updateStory(updateData: any, id: string): Observable { + return this.http.post( + this.baseUrl + 'update/' + id, + updateData); + } + + deleteStory(id: string) { + return this.http.get(this.baseUrl + 'delete/' + id); + } + + deleteAllStories(id: string) { + return this.http.get(this.baseUrl + 'deleteAllStories/' + id); + } + + updateFeedbackStatus(id: string, feedbackMarkup: string, hasComments: boolean) : Observable { + return this.http.post(this.baseUrl + "updateFeedbackStatus/" + id, {feedbackMarkup: feedbackMarkup, hasComments: hasComments}); + } + + updateFeedbackMarkup(id: string, feedbackMarkup: string) : Observable { + return this.http.post(this.baseUrl + "updateFeedbackMarkup/" + id, {feedbackMarkup: feedbackMarkup}); + } + + getFeedback(id: string) : Observable { + return this.http.get(this.baseUrl + "feedback/" + id); + } + + viewFeedback(id: string) : Observable { + return this.http.post(this.baseUrl + "viewFeedback/" + id, {}); + } + + getFeedbackAudio(id: string) : Observable { + return this.http.get(this.baseUrl + "feedbackAudio/" + id, {responseType: "blob"}); + } + + addFeedbackAudio(id: string, audioBlob: Blob) : Observable{ + let formData = new FormData(); + formData.append('audio', audioBlob); + return this.http.post(this.baseUrl + "addFeedbackAudio/" + id, formData); + } + + synthesise(id: string): Observable { + return this.http.get(this.baseUrl + 'synthesise/' + id); + } + + synthesiseObject(storyObject: Story): Observable { + return this.http.post(this.baseUrl + 'synthesiseObject/', {story: storyObject}); + } + + updateActiveRecording(storyId: string, recordingId: string): Observable { + return this.http.post(this.baseUrl + 'updateActiveRecording/' + storyId + '/', {activeRecording: recordingId}); + } + + averageWordCount(studentId:string, startDate:string, endDate:string) : Observable { + return this.http.post(this.baseUrl + "averageWordCount/" + studentId, {startDate:startDate, endDate:endDate}); + } + + countGrammarErrors(studentId:string) : Observable { + return this.http.get(this.baseUrl + "countGrammarErrors/" + studentId); + } + + getStoryStats() : Observable { + return this.http.get(this.baseUrl + "getStoryStats/allDB"); + }*/ +} diff --git a/ngapp/src/app/nav-bar/digital-reader/digital-reader.component.html b/ngapp/src/app/nav-bar/digital-reader/digital-reader.component.html new file mode 100644 index 000000000..fc55bf2eb --- /dev/null +++ b/ngapp/src/app/nav-bar/digital-reader/digital-reader.component.html @@ -0,0 +1,71 @@ +
+
+ {{ ts.l.digital_reader }} +
+
+

This is where the stories will be displayed/can be generated (I think)

+

List of stories in the db :

+

(will be filtered based in public/private/who created it)

+
+
+
+ + + + + + +
Story IDStory name
+
+ + + + + +
+ + + + Create a new story + + + + + +
Select the dialect voices to synthesise:

+
+
{{ts.l.munster}}
+
{{ts.l.connacht}}
+
{{ts.l.ulster}}
+

+ +
+ + +

+
+

+
Create
+
+
+
+
+
+
+ + +
+
+ +
+
+
+
\ No newline at end of file diff --git a/ngapp/src/app/nav-bar/digital-reader/digital-reader.component.scss b/ngapp/src/app/nav-bar/digital-reader/digital-reader.component.scss new file mode 100644 index 000000000..00f3e9306 --- /dev/null +++ b/ngapp/src/app/nav-bar/digital-reader/digital-reader.component.scss @@ -0,0 +1,177 @@ +.container { + padding-bottom: 20px; + width: 100%; +} + +.headerTitle { + text-align: center; +} + +.dialectTitle { + font-size: 15pt; + font-weight: bold; + padding-bottom: 10px; + padding-top: 30px; +} + +.storyImg { + width: 300px; + height: 200px; + padding: 5px; +} + +.panelDescription { + font-size : 15pt; + font-weight: bold; + justify-content: center; +} + +.panelFlex { + display:flex; + flex-direction: column; +} + +.panelDescriptionNoPhoto { + font-size : 15pt; + font-weight: bold; +} + +.moreResources { + place-content: space-between; +} + +.dialectChoice { + margin: auto; + width: inherit; + text-align: center; +} + +.dialectContainer { + display:flex; + flex-direction: column; + align-items: flex-start; + margin-right:auto; + margin-left: 0; + width:inherit; + gap: 1rem; +} + +.storyCreationActionRow { + flex-direction: column; +} + +.viewStory { + padding-right: 10px; +} + +.viewStoryBtn { + padding: 7px; + background-color: var(--scealai-green); + color: white; + border-radius: 4px; + text-decoration:none; + text-align: center; +} + + +button { + border: none +} + +button:disabled { + opacity: .5; +} + +.viewStoryBtn:hover { + cursor: pointer; +} + +.signIn { + font-weight: bold; + font-style: italic; + font-size: 16pt; + padding-top:40px; + text-align: center; +} + +/* The container
- needed to position the dropdown content */ + +.dropdown { + position: relative; + display: inline-block; + margin-top:-5px; +} + +/* Dropdown Content (Hidden by Default) */ +.dropdown-content { + display: none; + position: relative; + background-color: #f9f9f9; + /*min-width: 160px;*/ + box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); + /*z-index: 1;*/ +} + +/* Links inside the dropdown */ +.dropdown-content a { + color: black; + padding: 12px 16px; + text-decoration: none; + display: block; +} +/* Change color of dropdown links on hover */ +.dropdown-content a:hover {background-color: #f1f1f1} + +/* Show the dropdown menu on hover */ +.dropdown:hover .dropdown-content { + display: block; +} + +/* Change the background color of the dropdown button when the dropdown content is shown */ +.dropdown:hover .dropbtn { + background-color: #3e8e41; +} + +.footerLARA { + display: flex; + flex-direction: column; + bottom: 0; + width: 100%; + align-items: center; + background: #F5EEDE; +} + +.footerLogos { + display: flex; + flex-wrap: wrap; +} + +.copyright { + padding-top:10px; + padding-right:5px; + height: 15px; + opacity: 0.5; +} + +.logoPadding { + padding-left:50px; + padding-right:50px; + padding-top:5px; + padding-bottom:5px; +} + +.logoImg { + height: 40px; +} + +@media only screen and (max-width: 700px) { + .container { + width: 95%; + } + .content { + font-size: 10pt; + } + .header { + font-size: 15pt; + } +} \ No newline at end of file diff --git a/ngapp/src/app/nav-bar/digital-reader/digital-reader.component.spec.ts b/ngapp/src/app/nav-bar/digital-reader/digital-reader.component.spec.ts new file mode 100644 index 000000000..14e2fe2f2 --- /dev/null +++ b/ngapp/src/app/nav-bar/digital-reader/digital-reader.component.spec.ts @@ -0,0 +1,29 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { DigitalReaderComponent } from './digital-reader.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; + +describe('DigitalReaderComponent', () => { + let component: DigitalReaderComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + schemas: [ NO_ERRORS_SCHEMA ], + imports: [RouterTestingModule, HttpClientTestingModule], + declarations: [ DigitalReaderComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DigitalReaderComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); 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 new file mode 100644 index 000000000..23d5a71ee --- /dev/null +++ b/ngapp/src/app/nav-bar/digital-reader/digital-reader.component.ts @@ -0,0 +1,123 @@ +import { Component, OnInit } from '@angular/core'; +import { TranslationService } from 'app/core/services/translation.service'; +import { AuthenticationService } from 'app/core/services/authentication.service'; + +import { firstValueFrom } from "rxjs"; +import { User } from "app/core/models/user"; +import { UserService } from "app/core/services/user.service"; +//import { createClient } from "@supabase/supabase-js" +//import { Story } from "app/core/models/story"; + +import { HttpClient } from "@angular/common/http"; +//import { GrammarEngine } from 'lib/grammar-engine/grammar-engine'; +//import { anGramadoir } from "lib/grammar-engine/checkers/an-gramadoir"; +//import { CHECKBOXES, ERROR_TYPES, ErrorTag, GrammarChecker } from "lib/grammar-engine/types"; +import { DigitalReaderStoryService } from "app/core/services/dr-story.service" + +@Component({ + selector: 'app-digital-reader', + templateUrl: './digital-reader.component.html', + styleUrls: ['./digital-reader.component.scss'] +}) +export class DigitalReaderComponent implements OnInit { + + user: User; + tableData: Array; + htmlFolder: File | null = null; + + constructor( + public ts : TranslationService, + public auth: AuthenticationService, + public userService: UserService, + public drStoryService: DigitalReaderStoryService, + public http: HttpClient) { + + } + + async ngOnInit() { + const user = this.auth.getUserDetails(); + if (!user) return; + + // get logged-in user details + this.user = await firstValueFrom( this.userService.getUserById(user._id) ); + if (!this.user) return; + + console.log(this.user) + console.log(this) + + /*const supabaseUrl = "https://pdntukcptgktuzpynlsv.supabase.co" + const supabaseKey = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBkbnR1a2NwdGdrdHV6cHlubHN2Iiwicm9sZSI6ImFub24iLCJpYXQiOjE2NjIwMzA4ODAsImV4cCI6MTk3NzYwNjg4MH0.YrMhL9v3722XQUjpVlkj98waPI6c6xJ57zdmk7HUu0c" + + const supabase = createClient(supabaseUrl, supabaseKey) + + await this.supabaseLogin(supabase) + + const response = await this.testQueryDB(supabase) + + console.log(response) + + this.tableData = response //for testing*/ + + //this.getStory() + + const testInput = "scéal sách fada. Scéal le neart abairt. Scéal le carachtair escapable mar ' seo. scéal le \" seo chomh maith." + console.log(testInput) + + this.drStoryService.tokenizeSentence(testInput); + + //this.drStoryService.testChildProcess(); + + const segmentedSentences = await this.drStoryService.segmentText(document) + + console.log(segmentedSentences) + + //console.log(this.drStoryService.testChildProcess()); + + + + // check text for grammar errors + /*this.grammarEngine.check$(testInput).subscribe({ + next: (tag: ErrorTag) => { + // show error highlighting if button on + if (this.showErrorTags) { + this.quillHighlighter.addTag(tag); + } + }, + error: function (err) {console.error("ERROR GETTING THE 'CHECK()' RES: ", err)}, + complete: () => { + //if (!this.quillHighlighter) return; + + //save any grammar errors with associated sentences to DB + //this.grammarEngine.saveErrorsWithSentences(this.story._id).then(console.log, console.error); + //this.grammarLoaded = true; + }, + })*/ + + } + + + async supabaseLogin(supabase) { + const { data, error } = await supabase.auth.signInWithPassword({ + email: 'dr_test@example.com', + password: 'abair_digital_reader_test', + }) + } + + async testQueryDB(supabase) { + const { data, error } = await supabase + .from('dr_stories') + .select('id, name') + return data + } + + processUploadedFile(files: FileList) { + // in the future could make it so that the file is not removed if the dialog is simply opened again + this.htmlFolder = files.item(0) + console.log(this.htmlFolder) + } + + getHtmlDoc() { + + } + +} diff --git a/ngapp/src/app/nav-bar/nav-bar-routing.module.ts b/ngapp/src/app/nav-bar/nav-bar-routing.module.ts index 918dc0d4a..801792b4a 100644 --- a/ngapp/src/app/nav-bar/nav-bar-routing.module.ts +++ b/ngapp/src/app/nav-bar/nav-bar-routing.module.ts @@ -9,6 +9,7 @@ import { SponsorsComponent } from './sponsors/sponsors.component'; import { TeamComponent } from './team/team.component'; import { ReportAnIssueComponent } from './report-an-issue/report-an-issue.component'; import { ResourcesComponent } from './resources/resources.component'; +import { DigitalReaderComponent } from './digital-reader/digital-reader.component'; import { AboutTaidhginComponent } from './about-taidhgin/about-taidhgin.component'; import { FiosComponent } from './fios/fios.component'; @@ -18,6 +19,7 @@ const routes: Routes = [ { path: 'about-lara', component: AboutLaraComponent }, { path: 'technology', component: TechnologyComponent}, { path: 'resources', component: ResourcesComponent}, + { path: 'digital-reader', component: DigitalReaderComponent}, { path: 'team', component: TeamComponent}, { path: 'sponsors', component: SponsorsComponent}, { path: 'user-guides', component: UserGuidesComponent}, diff --git a/ngapp/src/app/nav-bar/nav-bar.module.ts b/ngapp/src/app/nav-bar/nav-bar.module.ts index c240f604a..d4b95e6ca 100644 --- a/ngapp/src/app/nav-bar/nav-bar.module.ts +++ b/ngapp/src/app/nav-bar/nav-bar.module.ts @@ -5,11 +5,15 @@ import { MatCardModule } from '@angular/material/card'; import { MatMenuModule } from '@angular/material/menu'; import { MatIconModule } from '@angular/material/icon'; +import { MatCheckboxModule } from '@angular/material/checkbox'; // added by David 04/07/2024 + import { NavBarRoutingModule } from './nav-bar-routing.module'; import { NavBarComponent } from './nav-bar/nav-bar.component'; import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { StudentModule } from '../student/student.module'; // added by David 05/07/2024 + import { AboutComponent } from './about/about.component'; import { AboutLaraComponent } from './about-lara/about-lara.component'; import { TechnologyComponent } from './technology/technology.component'; @@ -18,6 +22,7 @@ import { SponsorsComponent } from './sponsors/sponsors.component'; import { TeamComponent } from './team/team.component'; import { ReportAnIssueComponent } from './report-an-issue/report-an-issue.component'; import { ResourcesComponent } from './resources/resources.component'; +import { DigitalReaderComponent } from './digital-reader/digital-reader.component'; import { AboutTaidhginComponent } from './about-taidhgin/about-taidhgin.component'; import { FiosComponent } from './fios/fios.component'; @@ -32,6 +37,7 @@ import { FiosComponent } from './fios/fios.component'; TeamComponent, ReportAnIssueComponent, ResourcesComponent, + DigitalReaderComponent, AboutTaidhginComponent, FiosComponent ], @@ -45,6 +51,8 @@ import { FiosComponent } from './fios/fios.component'; MatCardModule, MatMenuModule, MatIconModule, + MatCheckboxModule, // added by David 04/07/2024 + StudentModule // added by David 05/07/2024 ] }) export class NavBarModule { }