diff --git a/README.md b/README.md index fa3ce84..96ababe 100644 --- a/README.md +++ b/README.md @@ -1 +1,51 @@ -# StudyOffline \ No newline at end of file +
+ StudyOffline +
+

StudyOffline

+ Built with ❤️ and 🍺 by + Philipp, + Pius and + contributors + +
+ +--- + +[![GitHub license](https://img.shields.io/github/license/piuswalter/StudyOffline)](https://github.com/piuswalter/StudyOffline/blob/main/LICENSE.md) +![GitHub last commit (branch)](https://img.shields.io/github/last-commit/piuswalter/StudyOffline/development) +[![GitHub issues](https://img.shields.io/github/issues/piuswalter/StudyOffline)](https://github.com/piuswalter/StudyOffline/issues) +![Lines of code](https://img.shields.io/tokei/lines/github/piuswalter/StudyOffline) +![GitHub language count](https://img.shields.io/github/languages/count/piuswalter/StudyOffline) + +StudyOffline is an open source tool that allows you to download the flashcards you have created on StudySmarter and study them offline. + +In addition, StudyOffline is a PWA (Progressive Web App) with which you can also learn your flashcards on any smartphone. + +## ⚒️ Setup your own StudyOffline instance + +[![Deploy with Docker](https://img.shields.io/badge/deploy%20with-docker-0db7ed)]() + +At the moment we do not have published StudyOffline to any container registry yet but you can easily build it by hand. + +The requirement is that Docker is installed. + +To do this, you just need to run + +`docker build https://github.com/piuswalter/StudyOffline.git#main -t studyoffline --no-cache` + +Now your container is built and can be started with + +`docker run -p 3000:3000 --name studyoffline -d studyoffline` + +You can access StudyOffline in your browser on `https://localhost:3000/`. + +## ⚙️ Built with latest technologies + +- [Express](https://expressjs.com/) - The web framework used at the backend +- [Angular](https://angular.io/) - The web framework used at the frontend +- [Node.js](https://nodejs.org/en/) - The backend power +- [IndexedDB](https://developer.mozilla.org/de/docs/Web/API/IndexedDB_API) - The database to store your flashcards + +## 📜 License + +This project is licensed under the AGPL-3.0 License - see the [LICENSE.md](LICENSE.md) file for details diff --git a/backend/src/utils/encoder.service.ts b/backend/src/utils/encoder.service.ts index 0251e67..316cf98 100644 --- a/backend/src/utils/encoder.service.ts +++ b/backend/src/utils/encoder.service.ts @@ -65,7 +65,8 @@ export default class FlashcardEncoder extends Readable { // eslint-disable-next-line class-methods-use-this async encodeFromURL(imageURL: string): Promise { - const { data } = await Axios.get(imageURL); - return Buffer.from(data, 'binary').toString('base64'); + const { data, headers } = await Axios.get(imageURL, { responseType: 'arraybuffer' }); + const prefix = `data:${headers['content-type']};base64, `; + return prefix + Buffer.from(data, 'binary').toString('base64'); } } diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json index 9fd6263..9954883 100644 --- a/frontend/.eslintrc.json +++ b/frontend/.eslintrc.json @@ -1,7 +1,8 @@ { "root": true, "ignorePatterns": [ - "projects/**/*" + "projects/**/*", + "*.spec.ts" ], "overrides": [ { diff --git a/frontend/angular.json b/frontend/angular.json index e609487..33f5110 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -27,7 +27,7 @@ "tsConfig": "tsconfig.app.json", "aot": true, "assets": [ - "src/favicon.ico", + "src/favicon.png", "src/assets", "src/manifest.webmanifest" ], @@ -95,7 +95,7 @@ "tsConfig": "tsconfig.spec.json", "karmaConfig": "karma.conf.js", "assets": [ - "src/favicon.ico", + "src/favicon.png", "src/assets", "src/manifest.webmanifest" ], diff --git a/frontend/ngsw-config.json b/frontend/ngsw-config.json index 607ab33..f57a3ff 100644 --- a/frontend/ngsw-config.json +++ b/frontend/ngsw-config.json @@ -7,7 +7,7 @@ "installMode": "prefetch", "resources": { "files": [ - "/favicon.ico", + "/favicon.png", "/index.html", "/manifest.webmanifest", "/*.css", diff --git a/frontend/package-lock.json b/frontend/package-lock.json index b509ac3..d89a494 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -6102,6 +6102,11 @@ "integrity": "sha512-qi86tE6hRcFHy8jI1m2VG+LaPUR1LhqDa5G8tVjuUXmOrpuAgqsA1pN0+ldgr3aKUH+QLI9hCY/OcRYisERejw==", "dev": true }, + "dexie": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dexie/-/dexie-3.0.3.tgz", + "integrity": "sha512-BSFhGpngnCl1DOr+8YNwBDobRMH0ziJs2vts69VilwetHYOtEDcLqo7d/XiIphM0tJZ2rPPyAGd31lgH2Ln3nw==" + }, "di": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", @@ -8485,7 +8490,8 @@ "ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true }, "inquirer": { "version": "7.3.3", diff --git a/frontend/package.json b/frontend/package.json index 48997c5..2e6c86a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,7 +4,7 @@ "scripts": { "ng": "ng", "start": "ng serve", - "build": "ng build", + "build": "ng build --prod", "test": "ng test", "lint": "eslint \"./src/**/*.{js,ts}\" --quiet --fix", "e2e": "ng e2e" @@ -22,6 +22,7 @@ "@angular/platform-browser-dynamic": "^11.0.9", "@angular/router": "^11.0.9", "@angular/service-worker": "^11.0.9", + "dexie": "^3.0.3", "rxjs": "^6.6.7", "tslib": "^2.1.0", "zone.js": "~0.10.2" diff --git a/frontend/src/app/_models/flashcard.class.ts b/frontend/src/app/_models/flashcard.class.ts new file mode 100644 index 0000000..de52c73 --- /dev/null +++ b/frontend/src/app/_models/flashcard.class.ts @@ -0,0 +1,33 @@ +import { Origin } from './origin.enum'; + +export interface IAnswer { + text: string; + isCorrect: boolean; +} + +interface FlashcardIndices { + id?: number; + subjectId: number; +} + +export abstract class Flashcard { + id?: number; + subjectId: number; + + constructor(ind: FlashcardIndices) { + this.id = ind.id; + this.subjectId = ind.subjectId; + } + + abstract readonly origin: Origin; + abstract question: string; + abstract answers: IAnswer[]; + abstract hints: string[]; + abstract solution: string; + abstract tags: number[]; + + get withoutId(): Flashcard { + delete this.id; + return this; + } +} diff --git a/frontend/src/app/_models/flashcard.interface.ts b/frontend/src/app/_models/flashcard.interface.ts deleted file mode 100644 index b12b90a..0000000 --- a/frontend/src/app/_models/flashcard.interface.ts +++ /dev/null @@ -1,17 +0,0 @@ -interface FlashcardAnswer { - text: string; - is_correct: boolean; -} - -export interface Flashcard { - id: number; - flashcardinfo: { - id: number; - creator: number; - question_html: FlashcardAnswer[]; - answer_html: FlashcardAnswer[]; - hint_html: any[]; - solution_html: string; - tags: any[]; - }; -} diff --git a/frontend/src/app/_models/internal/flashcard.class.ts b/frontend/src/app/_models/internal/flashcard.class.ts new file mode 100644 index 0000000..14d8005 --- /dev/null +++ b/frontend/src/app/_models/internal/flashcard.class.ts @@ -0,0 +1,29 @@ +import { Flashcard, IAnswer } from '../flashcard.class'; +import { Origin } from '../origin.enum'; + +export interface IInternalFlashcard { + id: number; + question: string; + answers: IAnswer[]; + hints: string[]; + solution: string; + tags: number[]; +} + +export class InternalFlashcard extends Flashcard { + readonly origin = Origin.internal; + question: string; + answers: IAnswer[]; + hints: string[]; + solution: string; + tags: number[]; + + constructor(flashcard: IInternalFlashcard, subjectId: number) { + super({ subjectId }); + this.question = flashcard.question; + this.answers = flashcard.answers; + this.hints = flashcard.hints; + this.solution = flashcard.solution; + this.tags = flashcard.tags; + } +} diff --git a/frontend/src/app/_models/origin.enum.ts b/frontend/src/app/_models/origin.enum.ts new file mode 100644 index 0000000..af0d92b --- /dev/null +++ b/frontend/src/app/_models/origin.enum.ts @@ -0,0 +1,5 @@ +// eslint-disable-next-line no-shadow +export enum Origin { + internal, + studySmarter +} diff --git a/frontend/src/app/_models/query-catalog.interface.ts b/frontend/src/app/_models/query-catalog.interface.ts deleted file mode 100644 index f6b92fe..0000000 --- a/frontend/src/app/_models/query-catalog.interface.ts +++ /dev/null @@ -1,10 +0,0 @@ -interface Flashcard { - question: string; - answer: string; -} - -export interface QueryCatalog { - id: number; - name: string; - flashcards: Flashcard[]; -} diff --git a/frontend/src/app/_models/studysmarter/flashcard.class.ts b/frontend/src/app/_models/studysmarter/flashcard.class.ts new file mode 100644 index 0000000..8424870 --- /dev/null +++ b/frontend/src/app/_models/studysmarter/flashcard.class.ts @@ -0,0 +1,41 @@ +import { IStudySmarterFlashcard } from '.'; +import { Flashcard, IAnswer } from '../flashcard.class'; +import { Origin } from '../origin.enum'; + +export class StudySmarterFlashcard extends Flashcard { + studySmarter: IStudySmarterFlashcard; + readonly origin = Origin.studySmarter; + + constructor(flashcard: IStudySmarterFlashcard, subjectId: number) { + super({ subjectId }); + this.studySmarter = flashcard; + } + + private get flashcardinfo() { + return this.studySmarter.flashcardinfo; + } + + get question(): string { + if (!this.flashcardinfo.question_html.length) return ''; + return this.flashcardinfo.question_html[0].text; + } + + get answers(): IAnswer[] { + return this.flashcardinfo.answer_html.map((answr) => ({ + text: answr.text, + isCorrect: answr.is_correct + })); + } + + get hints(): string[] { + return this.flashcardinfo.hint_html; + } + + get solution(): string { + return this.flashcardinfo.solution_html; + } + + get tags(): number[] { + return []; + } +} diff --git a/frontend/src/app/_models/studysmarter/flashcard.interface.ts b/frontend/src/app/_models/studysmarter/flashcard.interface.ts new file mode 100644 index 0000000..2d661c5 --- /dev/null +++ b/frontend/src/app/_models/studysmarter/flashcard.interface.ts @@ -0,0 +1,22 @@ +interface StudySmarterFlashcardAnswer { + text: string; + // eslint-disable-next-line @typescript-eslint/naming-convention + is_correct: boolean; +} + +export interface IStudySmarterFlashcard { + id?: number; + subjects: number[]; + + flashcardinfo: { + id?: number; + creator: number; + /* eslint-disable @typescript-eslint/naming-convention */ + question_html: StudySmarterFlashcardAnswer[]; + answer_html: StudySmarterFlashcardAnswer[]; + hint_html: string[]; + solution_html: string; + }; + community_applied_tag_ids: number[]; + /* eslint-enable @typescript-eslint/naming-convention */ +} diff --git a/frontend/src/app/_models/index.ts b/frontend/src/app/_models/studysmarter/index.ts similarity index 60% rename from frontend/src/app/_models/index.ts rename to frontend/src/app/_models/studysmarter/index.ts index 0fc594b..e384e24 100644 --- a/frontend/src/app/_models/index.ts +++ b/frontend/src/app/_models/studysmarter/index.ts @@ -1,5 +1,7 @@ +export * from './flashcard.class'; export * from './flashcard.interface'; export * from './login-request.interface'; export * from './login-response.interface'; -export * from './studysmarter-response.interface'; +export * from './response.interface'; +export * from './subject.class'; export * from './subject.interface'; diff --git a/frontend/src/app/_models/login-request.interface.ts b/frontend/src/app/_models/studysmarter/login-request.interface.ts similarity index 100% rename from frontend/src/app/_models/login-request.interface.ts rename to frontend/src/app/_models/studysmarter/login-request.interface.ts diff --git a/frontend/src/app/_models/login-response.interface.ts b/frontend/src/app/_models/studysmarter/login-response.interface.ts similarity index 100% rename from frontend/src/app/_models/login-response.interface.ts rename to frontend/src/app/_models/studysmarter/login-response.interface.ts diff --git a/frontend/src/app/_models/studysmarter-response.interface.ts b/frontend/src/app/_models/studysmarter/response.interface.ts similarity index 100% rename from frontend/src/app/_models/studysmarter-response.interface.ts rename to frontend/src/app/_models/studysmarter/response.interface.ts diff --git a/frontend/src/app/_models/studysmarter/subject.class.ts b/frontend/src/app/_models/studysmarter/subject.class.ts new file mode 100644 index 0000000..008a9c2 --- /dev/null +++ b/frontend/src/app/_models/studysmarter/subject.class.ts @@ -0,0 +1,24 @@ +import { IStudySmarterSubject } from '.'; +import { Flashcard } from '../flashcard.class'; +import { Subject } from '../subject.class'; + +export class StudySmarterSubject extends Subject { + studySmarter: IStudySmarterSubject; + + constructor(subject: IStudySmarterSubject, id?: number) { + super(id); + this.studySmarter = subject; + } + + get name(): string { + return this.studySmarter.name; + } + + get archived(): boolean { + return this.studySmarter.archived; + } + + get flashcards(): Flashcard[] { + return []; + } +} diff --git a/frontend/src/app/_models/subject.interface.ts b/frontend/src/app/_models/studysmarter/subject.interface.ts similarity index 86% rename from frontend/src/app/_models/subject.interface.ts rename to frontend/src/app/_models/studysmarter/subject.interface.ts index 522720b..921b680 100644 --- a/frontend/src/app/_models/subject.interface.ts +++ b/frontend/src/app/_models/studysmarter/subject.interface.ts @@ -1,5 +1,5 @@ /* eslint-disable camelcase */ -export interface Subject { +export interface IStudySmarterSubject { id: number; name: string; /* eslint-disable @typescript-eslint/naming-convention */ diff --git a/frontend/src/app/_models/subject.class.ts b/frontend/src/app/_models/subject.class.ts new file mode 100644 index 0000000..9f96792 --- /dev/null +++ b/frontend/src/app/_models/subject.class.ts @@ -0,0 +1,13 @@ +import { Flashcard } from './flashcard.class'; + +export abstract class Subject { + id?: number; + + constructor(id?: number) { + this.id = id; + } + + abstract name: string; + abstract archived: boolean; + abstract flashcards: Flashcard[]; +} diff --git a/frontend/src/app/_services/api.service.ts b/frontend/src/app/_services/api.service.ts index 96b6551..07f7d31 100644 --- a/frontend/src/app/_services/api.service.ts +++ b/frontend/src/app/_services/api.service.ts @@ -3,20 +3,21 @@ import { HttpErrorResponse, HttpEvent, HttpHeaders, - HttpRequest + HttpRequest, + HttpResponse } from '@angular/common/http'; -import { Injectable, NgZone } from '@angular/core'; +import { Injectable } from '@angular/core'; import { Observable, Subscription } from 'rxjs'; import { throwError } from 'rxjs/internal/observable/throwError'; import { retry, catchError } from 'rxjs/operators'; import { environment } from 'src/environments/environment'; import { - Flashcard, + IStudySmarterFlashcard, + IStudySmarterSubject, LoginRequest, LoginResponse, - StudySmarterResponse, - Subject -} from '../_models'; + StudySmarterResponse +} from '../_models/studysmarter'; import { StudySmarterService } from './study-smarter.service'; const handleError = (error: HttpErrorResponse): Observable => { @@ -36,18 +37,15 @@ const handleError = (error: HttpErrorResponse): Observable => { export class ApiService { constructor( private http: HttpClient, - private studySmarter: StudySmarterService, - private zone: NgZone + private studySmarter: StudySmarterService ) {} - private get httpOptions(): { headers: HttpHeaders } | any { - if (!this.studySmarter.apiToken) return {}; - return { - headers: new HttpHeaders({ - // eslint-disable-next-line @typescript-eslint/naming-convention - Authorization: `Token ${this.studySmarter.apiToken}` - }) - }; + private get httpHeaders(): HttpHeaders { + if (!this.studySmarter.apiToken) return {} as HttpHeaders; + return new HttpHeaders({ + // eslint-disable-next-line @typescript-eslint/naming-convention + Authorization: `Token ${this.studySmarter.apiToken}` + }); } login(data: LoginRequest, save: boolean, callback = () => {}): Subscription { @@ -74,32 +72,36 @@ export class ApiService { private fetchUserEndpoint( endpoint: string ): Observable> { - return ((this.http.get( - this.getApiUrl(endpoint), - this.httpOptions - ) as unknown) as Observable>).pipe( - retry(1), - catchError(handleError) - ); + return this.http + .get>(this.getApiUrl(endpoint), { + headers: this.httpHeaders + }) + .pipe(retry(1), catchError(handleError)); } private fetchUserProgressEndpoint( endpoint: string - ): Observable> { - return this.http.request( + ): Observable> { + return this.http.request( new HttpRequest('GET', this.getApiUrl(endpoint), { - ...this.httpOptions, + headers: this.httpHeaders, reportProgress: true, observe: 'events' }) ); } - getSubjects(): Observable> { - return this.fetchUserEndpoint('subjects'); + getSubjects(): Observable> { + return this.fetchUserEndpoint('subjects'); } - getFlashcards(subjectId: number): Observable> { - return this.fetchUserProgressEndpoint(`subjects/${subjectId}/flashcards`); + getFlashcards( + subjectId: number + ): Observable< + HttpEvent | HttpResponse + > { + return this.fetchUserProgressEndpoint( + `subjects/${subjectId}/flashcards` + ); } } diff --git a/frontend/src/app/_services/db.service.ts b/frontend/src/app/_services/db.service.ts new file mode 100644 index 0000000..02bf092 --- /dev/null +++ b/frontend/src/app/_services/db.service.ts @@ -0,0 +1,68 @@ +import { Injectable } from '@angular/core'; +import Dexie from 'dexie'; +import { from, Observable } from 'rxjs'; +import { Flashcard } from '../_models/flashcard.class'; +import { + IStudySmarterFlashcard, + IStudySmarterSubject, + StudySmarterFlashcard, + StudySmarterSubject +} from '../_models/studysmarter'; +import { Subject } from '../_models/subject.class'; + +@Injectable({ + providedIn: 'root' +}) +export class DbService extends Dexie { + subjects!: Dexie.Table; + flashcards!: Dexie.Table; + + constructor() { + super('StudyOffline'); + this.version(1).stores({ + subjects: '++id,&studySmarter.id', + flashcards: '++id,subjectId' + }); + } + + addSubject(subject: IStudySmarterSubject): Observable { + return from(this.subjects.add(new StudySmarterSubject(subject))); + } + + async getSubjects(): Promise { + const raw = await this.subjects.toArray(); + return raw.map( + (sub) => + new StudySmarterSubject( + (sub as StudySmarterSubject).studySmarter, + sub.id + ) + ); + } + + addFlashcards( + flashcards: IStudySmarterFlashcard[], + subjectId: number + ): Observable { + return from( + this.flashcards.bulkAdd( + flashcards.map((card) => new StudySmarterFlashcard(card, subjectId)) + ) + ); + } + + async getFlashcards(subjectId: number): Promise { + const raw = await this.flashcards.where({ subjectId }).toArray(); + return raw.map( + (card) => + new StudySmarterFlashcard( + (card as StudySmarterFlashcard).studySmarter, + card.subjectId + ) + ); + } + + clearAll(): void { + void this.delete(); + } +} diff --git a/frontend/src/app/_services/study-smarter.service.ts b/frontend/src/app/_services/study-smarter.service.ts index 6ce6ab4..38c38ca 100644 --- a/frontend/src/app/_services/study-smarter.service.ts +++ b/frontend/src/app/_services/study-smarter.service.ts @@ -1,6 +1,6 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { LoginResponse } from '../_models'; +import { LoginResponse } from '../_models/studysmarter'; @Injectable({ providedIn: 'root' }) export class StudySmarterService { @@ -46,4 +46,9 @@ export class StudySmarterService { this.id = userId; } } + + logout(): void { + localStorage.removeItem('StudySmarterToken'); + localStorage.removeItem('StudySmarterUserId'); + } } diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 201f390..10215e4 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -1,12 +1,10 @@ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { DownloadComponent } from './download/download.component'; -import { FeedbackComponent } from './feedback/feedback.component'; import { HelpComponent } from './help/help.component'; import { HomeComponent } from './home/home.component'; import { SettingsComponent } from './settings/settings.component'; import { StudySmarterLoginComponent } from './study-smarter-login/study-smarter-login.component'; -import { UploadComponent } from './upload/upload.component'; import { StudySmarterGuard } from './_services/study-smarter.guard'; const routes: Routes = [ @@ -15,10 +13,8 @@ const routes: Routes = [ component: DownloadComponent, canActivate: [StudySmarterGuard] }, - { path: 'feedback', component: FeedbackComponent }, { path: 'help', component: HelpComponent }, { path: 'settings', component: SettingsComponent }, - { path: 'upload', component: UploadComponent }, { path: 'login', component: StudySmarterLoginComponent }, { path: '', component: HomeComponent }, { path: '**', redirectTo: '' } diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html index 619c5a4..915e713 100644 --- a/frontend/src/app/app.component.html +++ b/frontend/src/app/app.component.html @@ -11,14 +11,11 @@ download - - upload - settings - - sentiment_satisfied_alt + + exit_to_app

diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index d15158c..dd44dfa 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -1,4 +1,8 @@ import { Component } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { LogoutDialogComponent } from './logout-dialog/logout-dialog.component'; +import { StudySmarterService } from './_services/study-smarter.service'; +import { Title } from '@angular/platform-browser'; @Component({ selector: 'app-root', @@ -6,5 +10,24 @@ import { Component } from '@angular/core'; styleUrls: ['./app.component.sass'] }) export class AppComponent { - title = 'frontend'; + title = 'StudyOffline'; + + constructor( + private dialog: MatDialog, + private studySmarter: StudySmarterService, + private titleService: Title + ) { + this.titleService.setTitle('StudyOffline'); + } + + get isLoggedIn(): boolean { + return this.studySmarter.isLoggedIn; + } + + openLogoutDialog(): void { + this.dialog.open(LogoutDialogComponent, { + panelClass: 'transparent', + disableClose: true + }); + } } diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 92a7869..e36ed25 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -21,8 +21,6 @@ import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatSelectModule } from '@angular/material/select'; import { MatToolbarModule } from '@angular/material/toolbar'; -import { UploadComponent } from './upload/upload.component'; -import { FeedbackComponent } from './feedback/feedback.component'; import { HelpComponent } from './help/help.component'; import { DownloadComponent } from './download/download.component'; import { HttpClientModule } from '@angular/common/http'; @@ -31,6 +29,7 @@ import { StudySmarterLoginComponent } from './study-smarter-login/study-smarter- import { StudySmarterService } from './_services/study-smarter.service'; import { SubjectSelectorComponent } from './download/subject-selector/subject-selector.component'; import { ProgressSpinnerDialogComponent } from './download/progress-spinner-dialog/progress-spinner-dialog.component'; +import { LogoutDialogComponent } from './logout-dialog/logout-dialog.component'; const initLocalStorage = (studySmarter: StudySmarterService) => { return (): void => studySmarter.loadCredentials(); @@ -41,13 +40,12 @@ const initLocalStorage = (studySmarter: StudySmarterService) => { AppComponent, HomeComponent, SettingsComponent, - UploadComponent, - FeedbackComponent, HelpComponent, DownloadComponent, StudySmarterLoginComponent, SubjectSelectorComponent, - ProgressSpinnerDialogComponent + ProgressSpinnerDialogComponent, + LogoutDialogComponent ], imports: [ BrowserModule, diff --git a/frontend/src/app/download/download.component.html b/frontend/src/app/download/download.component.html index 7a14960..b0836c3 100644 --- a/frontend/src/app/download/download.component.html +++ b/frontend/src/app/download/download.component.html @@ -32,6 +32,7 @@

Select subjects for download!

(click)="downloadSubjects()" class="full-width" color="primary" + [disabled]="!selectedSubjectIds.length" > Download diff --git a/frontend/src/app/download/download.component.ts b/frontend/src/app/download/download.component.ts index a2ab38c..cd46af9 100644 --- a/frontend/src/app/download/download.component.ts +++ b/frontend/src/app/download/download.component.ts @@ -1,11 +1,12 @@ -import { HttpEvent, HttpEventType } from '@angular/common/http'; +import { HttpEventType } from '@angular/common/http'; import { Component, OnInit, ViewChild } from '@angular/core'; import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { MatSelectionList } from '@angular/material/list'; import { Subscription } from 'rxjs'; -import { last, map } from 'rxjs/operators'; -import { Flashcard, Subject } from '../_models'; +import { first } from 'rxjs/operators'; +import { IStudySmarterSubject } from '../_models/studysmarter'; import { ApiService } from '../_services/api.service'; +import { DbService } from '../_services/db.service'; import { ProgressSpinnerDialogComponent } from './progress-spinner-dialog/progress-spinner-dialog.component'; @Component({ @@ -15,28 +16,32 @@ import { ProgressSpinnerDialogComponent } from './progress-spinner-dialog/progre }) export class DownloadComponent implements OnInit { @ViewChild('subjectList') subjectList: MatSelectionList | undefined; - private subjects: Subject[] = []; + private subjects: IStudySmarterSubject[] = []; - constructor(private apiService: ApiService, private dialog: MatDialog) {} + constructor( + private apiService: ApiService, + private dialog: MatDialog, + private dbService: DbService + ) {} ngOnInit(): void { this.fetchSubjects(); } - private cmpSubjectLastUsed(a: Subject, b: Subject) { + private cmpSubjectLastUsed(a: IStudySmarterSubject, b: IStudySmarterSubject) { return new Date(b.last_used).getTime() - new Date(a.last_used).getTime(); } - private filteredSubjects(active: boolean): Subject[] { + private filteredSubjects(active: boolean): IStudySmarterSubject[] { return this.subjects .filter((subject) => subject.archived !== active) .sort(this.cmpSubjectLastUsed.bind(this)); } - get activeSubjects(): Subject[] { + get activeSubjects(): IStudySmarterSubject[] { return this.filteredSubjects(true); } - get archivedSubjects(): Subject[] { + get archivedSubjects(): IStudySmarterSubject[] { return this.filteredSubjects(false); } @@ -82,31 +87,38 @@ export class DownloadComponent implements OnInit { const subscription = this.apiService .getFlashcards(subjectId) .pipe( - map((card: HttpEvent) => { - if (card.type === HttpEventType.DownloadProgress) { + first((event) => { + if (event.type === HttpEventType.DownloadProgress) { dialogRef.componentInstance.progress = (100 / toFetch) * ++fetched; subjectFetched++; } - return card; - }), - last() + return event.type === HttpEventType.Response; + }) ) - .subscribe((flashcards) => { + .subscribe((event) => { + if (!(event && event.type === HttpEventType.Response)) return; + const cards = event.body || []; + + const subjectIdx = this.subjects + .map((sub) => sub.id) + .indexOf(subjectId); + + if (subjectIdx !== -1) { + this.dbService + .addSubject(this.subjects[subjectIdx]) + .subscribe((dbSubjectId) => { + this.dbService.addFlashcards(cards, dbSubjectId).subscribe(); + }); + } + fetched += this.getFlashcardCount([subjectId]) - subjectFetched; dialogRef.componentInstance.progress = (100 / toFetch) * ++fetched; - console.log('Final flashcards: ', flashcards); - if (dialogRef.componentInstance.progress === 100) { + if (fetched === toFetch) { dialogRef.close(); } - // flashcards.map((card) => console.log('test')); - // console.log(flashcards.length); - // const answr = flashcards.results[0].flashcardinfo.answer_html[0].text; - // // images are now encoded within backend! - // console.log(answr); }); subscriptions.push(subscription); - // this.apiService.getFlashcards2(subjectId).subscribe(data => console.log); } dialogRef .afterClosed() diff --git a/frontend/src/app/download/subject-selector/subject-selector.component.html b/frontend/src/app/download/subject-selector/subject-selector.component.html index 3871b20..0a124c9 100644 --- a/frontend/src/app/download/subject-selector/subject-selector.component.html +++ b/frontend/src/app/download/subject-selector/subject-selector.component.html @@ -1,13 +1,13 @@ - -
-

- - {{ subject.name }} - -

-
-
-

Last used: {{ subject.last_used | date }}

-
+ +
+

+ + {{ subject.name }} + +

+
+
+

Last used: {{ subject.last_used | date }}

+
diff --git a/frontend/src/app/download/subject-selector/subject-selector.component.ts b/frontend/src/app/download/subject-selector/subject-selector.component.ts index ebb221c..61260ea 100644 --- a/frontend/src/app/download/subject-selector/subject-selector.component.ts +++ b/frontend/src/app/download/subject-selector/subject-selector.component.ts @@ -1,15 +1,13 @@ -import { Component, Input, OnInit } from '@angular/core'; -import { Subject } from 'src/app/_models'; +import { Component, Input } from '@angular/core'; +import { IStudySmarterSubject } from 'src/app/_models/studysmarter'; @Component({ selector: 'app-subject-selector', templateUrl: './subject-selector.component.html', styleUrls: ['./subject-selector.component.sass'] }) -export class SubjectSelectorComponent implements OnInit { - @Input() subject!: Subject; +export class SubjectSelectorComponent { + @Input() subject!: IStudySmarterSubject; constructor() {} - - ngOnInit(): void {} } diff --git a/frontend/src/app/feedback/feedback.component.html b/frontend/src/app/feedback/feedback.component.html deleted file mode 100644 index fd0d1e8..0000000 --- a/frontend/src/app/feedback/feedback.component.html +++ /dev/null @@ -1,3 +0,0 @@ -
-

feedback works!

-
diff --git a/frontend/src/app/feedback/feedback.component.sass b/frontend/src/app/feedback/feedback.component.sass deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/src/app/feedback/feedback.component.spec.ts b/frontend/src/app/feedback/feedback.component.spec.ts deleted file mode 100644 index 4d188e0..0000000 --- a/frontend/src/app/feedback/feedback.component.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { FeedbackComponent } from './feedback.component'; - -describe('FeedbackComponent', () => { - let component: FeedbackComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [FeedbackComponent] - }).compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(FeedbackComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/frontend/src/app/feedback/feedback.component.ts b/frontend/src/app/feedback/feedback.component.ts deleted file mode 100644 index 12da7e2..0000000 --- a/frontend/src/app/feedback/feedback.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component, OnInit } from '@angular/core'; - -@Component({ - selector: 'app-feedback', - templateUrl: './feedback.component.html', - styleUrls: ['./feedback.component.sass'] -}) -export class FeedbackComponent implements OnInit { - constructor() {} - - ngOnInit(): void {} -} diff --git a/frontend/src/app/help/help.component.html b/frontend/src/app/help/help.component.html index f8a37e6..d926801 100644 --- a/frontend/src/app/help/help.component.html +++ b/frontend/src/app/help/help.component.html @@ -18,17 +18,13 @@

Function of the different sites

download Download your StudySmarter flashcards - - upload - Upload your studysmarter.json file - settings Configure your settings - sentiment_satisfied_alt - Send us some Feedback + exit_to_app + Logout (with possibility to delete flashcards) diff --git a/frontend/src/app/help/help.component.ts b/frontend/src/app/help/help.component.ts index 3ec2f03..7390ee6 100644 --- a/frontend/src/app/help/help.component.ts +++ b/frontend/src/app/help/help.component.ts @@ -1,12 +1,10 @@ -import { Component, OnInit } from '@angular/core'; +import { Component } from '@angular/core'; @Component({ selector: 'app-help', templateUrl: './help.component.html', styleUrls: ['./help.component.sass'] }) -export class HelpComponent implements OnInit { +export class HelpComponent { constructor() {} - - ngOnInit(): void {} } diff --git a/frontend/src/app/home/flashcard.interface.ts b/frontend/src/app/home/flashcard.interface.ts deleted file mode 100644 index d8c6b65..0000000 --- a/frontend/src/app/home/flashcard.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface FlashCard { - question: string; - answer: string; -} diff --git a/frontend/src/app/home/home.component.html b/frontend/src/app/home/home.component.html index 3ce685d..d6b68ad 100644 --- a/frontend/src/app/home/home.component.html +++ b/frontend/src/app/home/home.component.html @@ -1,9 +1,12 @@
Select course - - - {{ course }} + + + {{ subject.name }} @@ -30,9 +33,13 @@ -
+

-
+
diff --git a/frontend/src/app/home/home.component.sass b/frontend/src/app/home/home.component.sass index db50250..f8fdac8 100644 --- a/frontend/src/app/home/home.component.sass +++ b/frontend/src/app/home/home.component.sass @@ -2,7 +2,14 @@ width: 100% .flashcard - margin: 5% 0 + margin: 5px 0 + +::ng-deep.flashcard-text + line-height: 2em + font-size: 2em + + img + max-width: 100% .title justify-content: center diff --git a/frontend/src/app/home/home.component.ts b/frontend/src/app/home/home.component.ts index 224a06b..58a54cd 100644 --- a/frontend/src/app/home/home.component.ts +++ b/frontend/src/app/home/home.component.ts @@ -1,53 +1,68 @@ -import { - AfterViewInit, - Component, - ElementRef, - OnInit, - ViewChild -} from '@angular/core'; -import { courses as courseList } from './test'; -import { FlashCard } from './flashcard.interface'; +import { Component } from '@angular/core'; +import { DbService } from '../_services/db.service'; +import { Subject } from '../_models/subject.class'; +import { Flashcard } from '../_models/flashcard.class'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; @Component({ selector: 'app-home', templateUrl: './home.component.html', styleUrls: ['./home.component.sass'] }) -export class HomeComponent implements OnInit, AfterViewInit { - @ViewChild('question') question!: ElementRef; - @ViewChild('answer') answer!: ElementRef; - - course = ''; - courseNames: string[]; +export class HomeComponent { + subjectId = 0; hideAnswer = true; - private cards: FlashCard[] = []; - private courses: any = courseList; - private index = 0; + question: SafeHtml = ''; + answer: SafeHtml = ''; + private cardIndex = 0; + private subjectMap: { [key: number]: Subject } = {}; + private flashcards: Flashcard[] = []; - constructor() { - this.courseNames = Object.keys(courseList); + constructor( + private dbService: DbService, + private domSanitizer: DomSanitizer + ) { + this.dbService + .getSubjects() + .then((subjects) => { + subjects.forEach((sub) => (this.subjectMap[sub.id || -1] = sub)); + if (subjects.length) this.subjectId = subjects[0].id || -1; + void this.dbService.getFlashcards(this.subjectId).then((cards) => { + this.flashcards = cards; + this.renderCard(); + }); + }) + .catch((err) => console.error(err)); } - ngOnInit(): void {} + get subject(): Subject { + return this.subjectMap[this.subjectId]; + } - ngAfterViewInit(): void { - this.course = this.courseNames ? this.courseNames[0] : ''; - this.changeCourse(this.course); - this.renderCard(); + get subjects(): Subject[] { + return Object.values(this.subjectMap); } - changeCourse(course: string): void { - if (this.courses[course]) { - this.cards = this.courses[course]; - } + async switchSubject(subjectId: number): Promise { + if (!this.subjectMap[subjectId]) return; + this.subjectId = subjectId; + this.cardIndex = 0; // reset index to circumvent array index out of bounds + this.flashcards = await this.dbService.getFlashcards(this.subjectId); + this.renderCard(); } renderCard(): void { this.hideAnswer = true; - if (this.cards[this.index]) { - const { question, answer } = this.cards[this.index]; - this.question.nativeElement.innerHTML = question; - this.answer.nativeElement.innerHTML = answer; + if (this.flashcards[this.cardIndex]) { + const { question, answers } = this.flashcards[this.cardIndex]; + this.question = this.domSanitizer.bypassSecurityTrustHtml(question); + if (answers.length !== 1) { + this.answer = '

Multiple Choice has not implemented yet

'; + } else { + this.answer = this.domSanitizer.bypassSecurityTrustHtml( + answers[0].text + ); + } } } @@ -56,10 +71,10 @@ export class HomeComponent implements OnInit, AfterViewInit { } switchCard(inc: number): void { - const cl = this.cards.length; + const cl = this.flashcards.length; if (!inc) inc = this.randomNumber(1, cl - 1); - this.index = (this.index + inc) % cl; - if (this.index < 0) this.index = cl - 1; + this.cardIndex = (this.cardIndex + inc) % cl; + if (this.cardIndex < 0) this.cardIndex = cl - 1; this.renderCard(); } } diff --git a/frontend/src/app/home/test.ts b/frontend/src/app/home/test.ts deleted file mode 100755 index 38472e2..0000000 --- a/frontend/src/app/home/test.ts +++ /dev/null @@ -1,136 +0,0 @@ -export const courses = { - 'AM - Klausur': [ - { - question: - '

Allgemeine Lösung: System von Differentialgleichungen

', - answer: - '

z.B.

' - }, - { - question: - '

Formel Eigenwerte

', - answer: - '


' - }, - { - question: - '

Schritte bei der Lösung von Differentialgleichungen

', - answer: - '

1. Separation der Variablen

2. Integral

3. Substitution

4. Integrieren

5. Auflösen



' - }, - { - question: - '

Substitutionsregel

', - answer: - '
' - }, - { - question: - '

Separation der Variablen (Differentialgleichung)

', - answer: - '
' - }, - { - question: - '

Ableitung von

', - answer: - '
' - }, - { - question: - '

Kreisfunktion (Einheitskreis)

', - answer: - '
' - }, - { - question: - '

Einheitskreismenge und Mittelpunkt

', - answer: - '
' - }, - { - question: - '

Definition Transformationssatz

', - answer: - '

Diffeomorphismus: Umkehrfunktion muss existieren.

' - }, - { - question: - '

Rechenregeln Integration Summe

', - answer: - '
' - }, - { - question: - '

Wann ist eine Funktion f integrierbar?

', - answer: - '
' - }, - { - question: - '

Lesbeguesche äußere Maß

', - answer: - '
' - }, - { - question: - '

Was ist die Idee bei der Integration?

', - answer: - '

Überdeckung mit Hüllquadern.

' - }, - { - question: - '

Kettenregel

', - answer: - '
' - }, - { - question: - '

Ablauf Backpropagation

', - answer: - '
' - }, - { - question: - '

Unterscheidung Extrema

', - answer: - '

Unterscheidung mithilfe der Determinanten der Hauptminoren. Bedingung muss für alle Determinanten gelten!

' - }, - { - question: - '

Definition Hessematrix

', - answer: - '
' - }, - { - question: - '

Eigenschaften des Differentials

', - answer: - '
' - }, - { - question: - '

Definition: Gradient

', - answer: - '
' - }, - { - question: - '

Differenzierbarkeit von Funktionen und das Differential einer Funktion

', - answer: - '
' - }, - { - question: - '

Logarithmusgesetze (4 Stück)

', - answer: - '
' - }, - { - question: - '

Wechsel der Logarithmusbasis

', - answer: - '
' - } - ] -}; diff --git a/frontend/src/app/logout-dialog/logout-dialog.component.html b/frontend/src/app/logout-dialog/logout-dialog.component.html new file mode 100644 index 0000000..fef8ea7 --- /dev/null +++ b/frontend/src/app/logout-dialog/logout-dialog.component.html @@ -0,0 +1,21 @@ +

Logout

+ +
+

You're about to get signed out

+

+ This will only sign you out of your account. If you also want to delete your + flashcards to save space, check the box below +

+ Delete Flashcards +
+ + + + diff --git a/frontend/src/app/logout-dialog/logout-dialog.component.sass b/frontend/src/app/logout-dialog/logout-dialog.component.sass new file mode 100644 index 0000000..6bf0e4d --- /dev/null +++ b/frontend/src/app/logout-dialog/logout-dialog.component.sass @@ -0,0 +1,2 @@ +.mat-dialog-content + overflow: inherit diff --git a/frontend/src/app/logout-dialog/logout-dialog.component.spec.ts b/frontend/src/app/logout-dialog/logout-dialog.component.spec.ts new file mode 100644 index 0000000..51d4528 --- /dev/null +++ b/frontend/src/app/logout-dialog/logout-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LogoutDialogComponent } from './logout-dialog.component'; + +describe('LogoutDialogComponent', () => { + let component: LogoutDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ LogoutDialogComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(LogoutDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/logout-dialog/logout-dialog.component.ts b/frontend/src/app/logout-dialog/logout-dialog.component.ts new file mode 100644 index 0000000..312a736 --- /dev/null +++ b/frontend/src/app/logout-dialog/logout-dialog.component.ts @@ -0,0 +1,25 @@ +import { Component } from '@angular/core'; +import { DbService } from '../_services/db.service'; +import { StudySmarterService } from '../_services/study-smarter.service'; + +@Component({ + selector: 'app-logout-dialog', + templateUrl: './logout-dialog.component.html', + styleUrls: ['./logout-dialog.component.sass'] +}) +export class LogoutDialogComponent { + deleteFlashcards = false; + + constructor( + private dbService: DbService, + private studySmarter: StudySmarterService + ) {} + + logout(): void { + if (this.deleteFlashcards) { + this.dbService.clearAll(); + } + this.studySmarter.logout(); + document.location.reload(); + } +} diff --git a/frontend/src/app/settings/settings.component.ts b/frontend/src/app/settings/settings.component.ts index 7476752..0a602e7 100644 --- a/frontend/src/app/settings/settings.component.ts +++ b/frontend/src/app/settings/settings.component.ts @@ -1,12 +1,10 @@ -import { Component, OnInit } from '@angular/core'; +import { Component } from '@angular/core'; @Component({ selector: 'app-settings', templateUrl: './settings.component.html', styleUrls: ['./settings.component.sass'] }) -export class SettingsComponent implements OnInit { +export class SettingsComponent { constructor() {} - - ngOnInit(): void {} } diff --git a/frontend/src/app/study-smarter-login/study-smarter-login.component.html b/frontend/src/app/study-smarter-login/study-smarter-login.component.html index cf49b7d..a4511b3 100644 --- a/frontend/src/app/study-smarter-login/study-smarter-login.component.html +++ b/frontend/src/app/study-smarter-login/study-smarter-login.component.html @@ -1,41 +1,34 @@
-
+
+ + + + diff --git a/frontend/src/app/study-smarter-login/study-smarter-login.component.sass b/frontend/src/app/study-smarter-login/study-smarter-login.component.sass index f412d55..d4e9208 100644 --- a/frontend/src/app/study-smarter-login/study-smarter-login.component.sass +++ b/frontend/src/app/study-smarter-login/study-smarter-login.component.sass @@ -1,6 +1,8 @@ .login-form - margin: 7% 36% + margin: 7% auto padding: 50px 15px + min-width: 320px + max-width: 450px .spacer margin-top: 20px diff --git a/frontend/src/app/upload/upload.component.html b/frontend/src/app/upload/upload.component.html deleted file mode 100644 index 6aa9a1d..0000000 --- a/frontend/src/app/upload/upload.component.html +++ /dev/null @@ -1,3 +0,0 @@ -
-

upload works!

-
diff --git a/frontend/src/app/upload/upload.component.sass b/frontend/src/app/upload/upload.component.sass deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/src/app/upload/upload.component.spec.ts b/frontend/src/app/upload/upload.component.spec.ts deleted file mode 100644 index e6b0002..0000000 --- a/frontend/src/app/upload/upload.component.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { UploadComponent } from './upload.component'; - -describe('UploadComponent', () => { - let component: UploadComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [UploadComponent] - }).compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(UploadComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/frontend/src/app/upload/upload.component.ts b/frontend/src/app/upload/upload.component.ts deleted file mode 100644 index cd757c9..0000000 --- a/frontend/src/app/upload/upload.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component, OnInit } from '@angular/core'; - -@Component({ - selector: 'app-upload', - templateUrl: './upload.component.html', - styleUrls: ['./upload.component.sass'] -}) -export class UploadComponent implements OnInit { - constructor() {} - - ngOnInit(): void {} -} diff --git a/frontend/src/assets/icons/icon-128x128.png b/frontend/src/assets/icons/icon-128x128.png old mode 100644 new mode 100755 index 9f9241f..22e121e Binary files a/frontend/src/assets/icons/icon-128x128.png and b/frontend/src/assets/icons/icon-128x128.png differ diff --git a/frontend/src/assets/icons/icon-144x144.png b/frontend/src/assets/icons/icon-144x144.png old mode 100644 new mode 100755 index 4a5f8c1..4291b95 Binary files a/frontend/src/assets/icons/icon-144x144.png and b/frontend/src/assets/icons/icon-144x144.png differ diff --git a/frontend/src/assets/icons/icon-152x152.png b/frontend/src/assets/icons/icon-152x152.png old mode 100644 new mode 100755 index 34a1a8d..034b177 Binary files a/frontend/src/assets/icons/icon-152x152.png and b/frontend/src/assets/icons/icon-152x152.png differ diff --git a/frontend/src/assets/icons/icon-192x192.png b/frontend/src/assets/icons/icon-192x192.png old mode 100644 new mode 100755 index 9172e5d..3182875 Binary files a/frontend/src/assets/icons/icon-192x192.png and b/frontend/src/assets/icons/icon-192x192.png differ diff --git a/frontend/src/assets/icons/icon-384x384.png b/frontend/src/assets/icons/icon-384x384.png old mode 100644 new mode 100755 index e54e8d3..674d625 Binary files a/frontend/src/assets/icons/icon-384x384.png and b/frontend/src/assets/icons/icon-384x384.png differ diff --git a/frontend/src/assets/icons/icon-512x512.png b/frontend/src/assets/icons/icon-512x512.png old mode 100644 new mode 100755 index 51ee297..f4f5e77 Binary files a/frontend/src/assets/icons/icon-512x512.png and b/frontend/src/assets/icons/icon-512x512.png differ diff --git a/frontend/src/assets/icons/icon-72x72.png b/frontend/src/assets/icons/icon-72x72.png old mode 100644 new mode 100755 index 2814a3f..d5c088a Binary files a/frontend/src/assets/icons/icon-72x72.png and b/frontend/src/assets/icons/icon-72x72.png differ diff --git a/frontend/src/assets/icons/icon-96x96.png b/frontend/src/assets/icons/icon-96x96.png old mode 100644 new mode 100755 index d271025..fbb4204 Binary files a/frontend/src/assets/icons/icon-96x96.png and b/frontend/src/assets/icons/icon-96x96.png differ diff --git a/frontend/src/favicon.ico b/frontend/src/favicon.ico deleted file mode 100644 index 997406a..0000000 Binary files a/frontend/src/favicon.ico and /dev/null differ diff --git a/frontend/src/favicon.png b/frontend/src/favicon.png new file mode 100755 index 0000000..487d3fe Binary files /dev/null and b/frontend/src/favicon.png differ diff --git a/frontend/src/index.html b/frontend/src/index.html index 0eff801..8cc6653 100644 --- a/frontend/src/index.html +++ b/frontend/src/index.html @@ -5,7 +5,7 @@ Frontend - + diff --git a/frontend/src/manifest.webmanifest b/frontend/src/manifest.webmanifest index a5c237d..7311768 100644 --- a/frontend/src/manifest.webmanifest +++ b/frontend/src/manifest.webmanifest @@ -1,7 +1,7 @@ { - "name": "frontend", - "short_name": "frontend", - "theme_color": "#1976d2", + "name": "StudyOffline", + "short_name": "StudyOffline", + "theme_color": "#7b1fa2", "background_color": "#fafafa", "display": "standalone", "scope": "./", diff --git a/logo.png b/logo.png new file mode 100755 index 0000000..487d3fe Binary files /dev/null and b/logo.png differ