Compare commits
21 Commits
a3adfa8ee0
...
signals-co
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4f887bf33 | ||
|
|
61ec781bbb | ||
|
|
3ee41cf571 | ||
|
|
7f2e762a44 | ||
|
|
cfa3015a7b | ||
|
|
778a92fd5e | ||
| ff52a198dc | |||
|
|
f3d59a0ef3 | ||
|
|
d84485e52b | ||
|
|
f789d89995 | ||
|
|
7e0174bcc2 | ||
|
|
fe1d59a3bb | ||
|
|
69a99c9312 | ||
|
|
a6414ae64d | ||
|
|
9cf47f0e2a | ||
|
|
6c89562dc3 | ||
|
|
e85eabbed5 | ||
|
|
1ec4ba8212 | ||
|
|
a1ff181443 | ||
|
|
ee8f48bc43 | ||
|
|
7ec1aee884 |
@@ -7,6 +7,6 @@ RUN npm run build-prod-fr
|
|||||||
|
|
||||||
FROM nginx:1.27-alpine AS final
|
FROM nginx:1.27-alpine AS final
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=builder /app/dist/codiki-ng/en/browser /usr/share/nginx/html/en/
|
COPY --from=builder /app/dist/codiki/en/browser /usr/share/nginx/html/en/
|
||||||
COPY --from=builder /app/dist/codiki-ng/fr/browser /usr/share/nginx/html/fr/
|
COPY --from=builder /app/dist/codiki/fr/browser/fr /usr/share/nginx/html/fr/
|
||||||
COPY frontend/conf/nginx.conf /etc/nginx/nginx.conf
|
COPY frontend/conf/nginx.conf /etc/nginx/nginx.conf
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
"version": 1,
|
"version": 1,
|
||||||
"newProjectRoot": "projects",
|
"newProjectRoot": "projects",
|
||||||
"projects": {
|
"projects": {
|
||||||
"codiki-ng": {
|
"codiki": {
|
||||||
"projectType": "application",
|
"projectType": "application",
|
||||||
"schematics": {
|
"schematics": {
|
||||||
"@schematics/angular:component": {
|
"@schematics/angular:component": {
|
||||||
@@ -14,16 +14,19 @@
|
|||||||
"sourceRoot": "src",
|
"sourceRoot": "src",
|
||||||
"prefix": "app",
|
"prefix": "app",
|
||||||
"i18n": {
|
"i18n": {
|
||||||
"sourceLocale": "en-UK",
|
"sourceLocale": "en",
|
||||||
"locales": {
|
"locales": {
|
||||||
"fr": "src/locale/messages-fr.json"
|
"fr": {
|
||||||
|
"translation": "src/locale/messages-fr.json"
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"architect": {
|
"architect": {
|
||||||
"build": {
|
"build": {
|
||||||
"builder": "@angular-devkit/build-angular:application",
|
"builder": "@angular-devkit/build-angular:application",
|
||||||
"options": {
|
"options": {
|
||||||
"outputPath": "dist/codiki-ng",
|
"outputPath": "dist/codiki",
|
||||||
"index": "src/index.html",
|
"index": "src/index.html",
|
||||||
"browser": "src/main.ts",
|
"browser": "src/main.ts",
|
||||||
"polyfills": [
|
"polyfills": [
|
||||||
@@ -56,7 +59,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outputHashing": "all",
|
"outputHashing": "all",
|
||||||
"outputPath": "dist/codiki-ng/en/"
|
"outputPath": "dist/codiki/en/"
|
||||||
},
|
},
|
||||||
"production-fr": {
|
"production-fr": {
|
||||||
"budgets": [
|
"budgets": [
|
||||||
@@ -71,8 +74,10 @@
|
|||||||
"maximumError": "4kb"
|
"maximumError": "4kb"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"localize": ["fr"],
|
||||||
|
"i18nMissingTranslation": "error",
|
||||||
"outputHashing": "all",
|
"outputHashing": "all",
|
||||||
"outputPath": "dist/codiki-ng/fr/"
|
"outputPath": "dist/codiki/fr/"
|
||||||
},
|
},
|
||||||
"development": {
|
"development": {
|
||||||
"optimization": false,
|
"optimization": false,
|
||||||
@@ -80,18 +85,18 @@
|
|||||||
"sourceMap": true
|
"sourceMap": true
|
||||||
},
|
},
|
||||||
"en": {
|
"en": {
|
||||||
"outputPath": "dist/codiki-ng/en/",
|
"outputPath": "dist/codiki/en/",
|
||||||
"optimization": false,
|
"optimization": false,
|
||||||
"extractLicenses": false,
|
"extractLicenses": false,
|
||||||
"sourceMap": true
|
"sourceMap": true
|
||||||
},
|
},
|
||||||
"fr": {
|
"fr": {
|
||||||
"outputPath": "dist/codiki-ng/fr/",
|
"outputPath": "dist/codiki/fr/",
|
||||||
"optimization": false,
|
"optimization": false,
|
||||||
"extractLicenses": false,
|
"extractLicenses": false,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"localize": ["fr"],
|
"localize": ["fr"],
|
||||||
"i18nMissingTranslation": "error"
|
"i18nMissingTranslation": "warning"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"defaultConfiguration": ""
|
"defaultConfiguration": ""
|
||||||
@@ -100,19 +105,19 @@
|
|||||||
"builder": "@angular-devkit/build-angular:dev-server",
|
"builder": "@angular-devkit/build-angular:dev-server",
|
||||||
"configurations": {
|
"configurations": {
|
||||||
"production-en": {
|
"production-en": {
|
||||||
"buildTarget": "codiki-ng:build:production-en"
|
"buildTarget": "codiki:build:production-en"
|
||||||
},
|
},
|
||||||
"production-fr": {
|
"production-fr": {
|
||||||
"buildTarget": "codiki-ng:build:production-fr"
|
"buildTarget": "codiki:build:production-fr"
|
||||||
},
|
},
|
||||||
"development": {
|
"development": {
|
||||||
"buildTarget": "codiki-ng:build:development"
|
"buildTarget": "codiki:build:development"
|
||||||
},
|
},
|
||||||
"en": {
|
"en": {
|
||||||
"buildTarget": "codiki-ng:build:en"
|
"buildTarget": "codiki:build:en"
|
||||||
},
|
},
|
||||||
"fr": {
|
"fr": {
|
||||||
"buildTarget": "codiki-ng:build:fr"
|
"buildTarget": "codiki:build:fr"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"defaultConfiguration": "development"
|
"defaultConfiguration": "development"
|
||||||
@@ -120,7 +125,7 @@
|
|||||||
"extract-i18n": {
|
"extract-i18n": {
|
||||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||||
"options": {
|
"options": {
|
||||||
"buildTarget": "codiki-ng:build"
|
"buildTarget": "codiki:build"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"test": {
|
"test": {
|
||||||
|
|||||||
5017
frontend/package-lock.json
generated
5017
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@
|
|||||||
"start-fr": "ng serve --port 4201 --configuration=fr --proxy-config proxy.conf.json",
|
"start-fr": "ng serve --port 4201 --configuration=fr --proxy-config proxy.conf.json",
|
||||||
"build": "ng build",
|
"build": "ng build",
|
||||||
"build-prod-en": "ng build --configuration=production-en --base-href /en/",
|
"build-prod-en": "ng build --configuration=production-en --base-href /en/",
|
||||||
"build-prod-fr": "ng build --configuration=production-fr --base-href /fr/",
|
"build-prod-fr": "ng build --configuration=production-fr --base-href",
|
||||||
"watch": "ng build --watch --configuration development",
|
"watch": "ng build --watch --configuration development",
|
||||||
"test": "ng test",
|
"test": "ng test",
|
||||||
"i18n": "npm run i18n-ng-extraction && npm run i18n-fr-file-completion",
|
"i18n": "npm run i18n-ng-extraction && npm run i18n-fr-file-completion",
|
||||||
@@ -17,25 +17,25 @@
|
|||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^17.0.0",
|
"@angular/animations": "^18.2.5",
|
||||||
"@angular/cdk": "^17.3.1",
|
"@angular/cdk": "^18.2.5",
|
||||||
"@angular/common": "^17.0.0",
|
"@angular/common": "^18.2.5",
|
||||||
"@angular/compiler": "^17.0.0",
|
"@angular/compiler": "^18.2.5",
|
||||||
"@angular/core": "^17.0.0",
|
"@angular/core": "^18.2.5",
|
||||||
"@angular/forms": "^17.0.0",
|
"@angular/forms": "^18.2.5",
|
||||||
"@angular/material": "^17.3.1",
|
"@angular/material": "^18.2.5",
|
||||||
"@angular/platform-browser": "^17.0.0",
|
"@angular/platform-browser": "^18.2.5",
|
||||||
"@angular/platform-browser-dynamic": "^17.0.0",
|
"@angular/platform-browser-dynamic": "^18.2.5",
|
||||||
"@angular/router": "^17.0.0",
|
"@angular/router": "^18.2.5",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"zone.js": "~0.14.2"
|
"zone.js": "~0.14.10"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-devkit/build-angular": "^17.0.5",
|
"@angular-devkit/build-angular": "^18.2.5",
|
||||||
"@angular/cli": "^17.0.5",
|
"@angular/cli": "^18.2.5",
|
||||||
"@angular/compiler-cli": "^17.0.0",
|
"@angular/compiler-cli": "^18.2.5",
|
||||||
"@angular/localize": "^17.3.12",
|
"@angular/localize": "^18.2.5",
|
||||||
"@types/jasmine": "~5.1.0",
|
"@types/jasmine": "~5.1.0",
|
||||||
"jasmine-core": "~5.1.0",
|
"jasmine-core": "~5.1.0",
|
||||||
"karma": "~6.4.0",
|
"karma": "~6.4.0",
|
||||||
@@ -43,6 +43,6 @@
|
|||||||
"karma-coverage": "~2.2.0",
|
"karma-coverage": "~2.2.0",
|
||||||
"karma-jasmine": "~5.1.0",
|
"karma-jasmine": "~5.1.0",
|
||||||
"karma-jasmine-html-reporter": "~2.1.0",
|
"karma-jasmine-html-reporter": "~2.1.0",
|
||||||
"typescript": "~5.2.2"
|
"typescript": "~5.5.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ApplicationConfig } from '@angular/core';
|
import { ApplicationConfig, provideExperimentalZonelessChangeDetection } from '@angular/core';
|
||||||
import { provideRouter, withRouterConfig } from '@angular/router';
|
import { provideRouter, withRouterConfig } from '@angular/router';
|
||||||
|
|
||||||
import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
|
import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
|
||||||
@@ -16,7 +16,9 @@ export const appConfig: ApplicationConfig = {
|
|||||||
})
|
})
|
||||||
),
|
),
|
||||||
provideAnimationsAsync(),
|
provideAnimationsAsync(),
|
||||||
|
provideExperimentalZonelessChangeDetection(),
|
||||||
provideHttpClient(withInterceptorsFromDi()),
|
provideHttpClient(withInterceptorsFromDi()),
|
||||||
|
|
||||||
{ provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
|
{ provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
@for(category of categories$ | async; track category) {
|
@for(category of categories(); track category) {
|
||||||
<div class="category {{category.isOpenned ? 'openned' : ''}}">
|
<div class="category {{category.isOpenned ? 'openned' : ''}}">
|
||||||
<div id="category-{{category.id}}" class="category-header" (click)="setOpenned(category)">
|
<div id="category-{{category.id}}" class="category-header" (click)="setOpenned(category)">
|
||||||
{{category.name}}
|
{{category.name}}
|
||||||
<mat-icon>chevron_right</mat-icon>
|
<mat-icon>chevron_right</mat-icon>
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { CommonModule } from "@angular/common";
|
import { CommonModule } from "@angular/common";
|
||||||
import { Component, EventEmitter, inject, OnInit, Output } from "@angular/core";
|
import { Component, EventEmitter, inject, OnDestroy, OnInit, Output, signal } from "@angular/core";
|
||||||
import { MatIconModule } from "@angular/material/icon";
|
import { MatIconModule } from "@angular/material/icon";
|
||||||
import { DisplayableCategory, SideMenuService } from "../side-menu.service";
|
import { DisplayableCategory, SideMenuService } from "../side-menu.service";
|
||||||
import { Observable } from "rxjs";
|
import { Observable, Subscription } from "rxjs";
|
||||||
import { RouterModule } from "@angular/router";
|
import { RouterModule } from "@angular/router";
|
||||||
|
import { Category } from "../../../core/rest-services/category/model/category";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-categories-menu',
|
selector: 'app-categories-menu',
|
||||||
@@ -12,17 +13,21 @@ import { RouterModule } from "@angular/router";
|
|||||||
templateUrl: './categories-menu.component.html',
|
templateUrl: './categories-menu.component.html',
|
||||||
styleUrl: './categories-menu.component.scss'
|
styleUrl: './categories-menu.component.scss'
|
||||||
})
|
})
|
||||||
export class CategoriesMenuComponent implements OnInit {
|
export class CategoriesMenuComponent implements OnInit, OnDestroy {
|
||||||
private sideMenuService = inject(SideMenuService);
|
|
||||||
@Output()
|
@Output()
|
||||||
categoryClicked = new EventEmitter<void>();
|
categoryClicked = new EventEmitter<void>();
|
||||||
|
private sideMenuService = inject(SideMenuService);
|
||||||
|
private categoriesSubscription?: Subscription;
|
||||||
|
categories = signal<DisplayableCategory[]>([]);
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.categoriesSubscription = this.sideMenuService.categories$
|
||||||
|
.subscribe(categories => this.categories.set(categories));
|
||||||
this.sideMenuService.loadCategories();
|
this.sideMenuService.loadCategories();
|
||||||
}
|
}
|
||||||
|
|
||||||
get categories$(): Observable<DisplayableCategory[]> {
|
ngOnDestroy(): void {
|
||||||
return this.sideMenuService.categories$;
|
this.categoriesSubscription?.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
setOpenned(category: DisplayableCategory): void {
|
setOpenned(category: DisplayableCategory): void {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<div class="menu {{ isOpenned ? 'displayed' : '' }}">
|
<div class="menu {{ isOpenned() ? 'displayed' : '' }}">
|
||||||
<h1>
|
<h1>
|
||||||
<a [routerLink]="['/home']">
|
<a [routerLink]="['/home']">
|
||||||
<img src="assets/images/codiki.png" alt="logo"/>
|
<img src="assets/images/codiki.png" alt="logo"/>
|
||||||
@@ -11,4 +11,4 @@
|
|||||||
<h2 i18n>Categories</h2>
|
<h2 i18n>Categories</h2>
|
||||||
<app-categories-menu (categoryClicked)="close()"></app-categories-menu>
|
<app-categories-menu (categoryClicked)="close()"></app-categories-menu>
|
||||||
</div>
|
</div>
|
||||||
<div class="overlay {{ isOpenned ? 'displayed' : ''}}" (click)="close()"></div>
|
<div class="overlay {{ isOpenned() ? 'displayed' : ''}}" (click)="close()"></div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component, signal } from '@angular/core';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
@@ -12,13 +12,13 @@ import { CategoriesMenuComponent } from './categories-menu/categories-menu.compo
|
|||||||
styleUrl: './side-menu.component.scss'
|
styleUrl: './side-menu.component.scss'
|
||||||
})
|
})
|
||||||
export class SideMenuComponent {
|
export class SideMenuComponent {
|
||||||
isOpenned: boolean = false;
|
isOpenned = signal(false);
|
||||||
|
|
||||||
open(): void {
|
open(): void {
|
||||||
this.isOpenned = true;
|
this.isOpenned.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
close(): void {
|
close(): void {
|
||||||
this.isOpenned = false;
|
this.isOpenned.set(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<h1 i18n>Last publications</h1>
|
<h1 i18n>Last publications</h1>
|
||||||
@if ((isLoading$ | async) === true) {
|
@if (isLoading()) {
|
||||||
<h2 i18n>Publications loading...</h2>
|
<h2 i18n>Publications loading...</h2>
|
||||||
<mat-spinner></mat-spinner>
|
<mat-spinner></mat-spinner>
|
||||||
} @else {
|
} @else {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Component, OnInit, inject } from '@angular/core';
|
import { Component, OnDestroy, OnInit, inject, signal } from '@angular/core';
|
||||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable, Subscription } from 'rxjs';
|
||||||
import { PublicationListComponent } from '../../components/publication-list/publication-list.component';
|
import { PublicationListComponent } from '../../components/publication-list/publication-list.component';
|
||||||
import { Publication } from '../../core/rest-services/publications/model/publication';
|
import { Publication } from '../../core/rest-services/publications/model/publication';
|
||||||
import { HomeService } from './home.service';
|
import { HomeService } from './home.service';
|
||||||
@@ -18,18 +18,27 @@ import { CommonModule } from '@angular/common';
|
|||||||
styleUrl: './home.component.scss',
|
styleUrl: './home.component.scss',
|
||||||
providers: [HomeService]
|
providers: [HomeService]
|
||||||
})
|
})
|
||||||
export class HomeComponent implements OnInit {
|
export class HomeComponent implements OnInit, OnDestroy {
|
||||||
private homeService = inject(HomeService);
|
private homeService = inject(HomeService);
|
||||||
|
private isLoadingSubscription?: Subscription;
|
||||||
get isLoading$(): Observable<boolean> {
|
private publicationsSubscription?: Subscription;
|
||||||
return this.homeService.isLoading$;
|
isLoading = signal(false);
|
||||||
}
|
publicationsIsEmpty = signal(true);
|
||||||
|
|
||||||
get publications$(): Observable<Publication[]> {
|
get publications$(): Observable<Publication[]> {
|
||||||
return this.homeService.publications$;
|
return this.homeService.publications$;
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.isLoadingSubscription = this.homeService.isLoading$
|
||||||
|
.subscribe(isLoading => this.isLoading.set(isLoading));
|
||||||
|
this.publicationsSubscription = this.homeService.publications$
|
||||||
|
.subscribe(publications => this.publicationsIsEmpty.set(publications.length > 0));
|
||||||
this.homeService.startLatestPublicationsRetrieving();
|
this.homeService.startLatestPublicationsRetrieving();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.isLoadingSubscription?.unsubscribe();
|
||||||
|
this.publicationsSubscription?.unsubscribe();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,11 @@
|
|||||||
+
|
+
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@if ((isLoading$ | async) === true) {
|
@if (isLoading()) {
|
||||||
<h2 i18n>Publication loading...</h2>
|
<h2 i18n>Publication loading...</h2>
|
||||||
<mat-spinner></mat-spinner>
|
<mat-spinner></mat-spinner>
|
||||||
} @else {
|
} @else {
|
||||||
@if ((isLoaded$ | async) === true) {
|
@if (isLoaded()) {
|
||||||
<app-publication-list [publications$]="publications$"></app-publication-list>
|
<app-publication-list [publications$]="publications$"></app-publication-list>
|
||||||
} @else {
|
} @else {
|
||||||
<h2 i18n>There is no any publication...</h2>
|
<h2 i18n>There is no any publication...</h2>
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import { Component, inject, OnInit } from "@angular/core";
|
import { Component, inject, OnDestroy, OnInit, signal } from "@angular/core";
|
||||||
import { MatProgressSpinnerModule } from "@angular/material/progress-spinner";
|
import { MatProgressSpinnerModule } from "@angular/material/progress-spinner";
|
||||||
import { MyPublicationsService } from "./my-publications.service";
|
import { MyPublicationsService } from "./my-publications.service";
|
||||||
import { Observable } from "rxjs";
|
import { Observable, Subscription } from "rxjs";
|
||||||
import { PublicationListComponent } from "../../components/publication-list/publication-list.component";
|
import { PublicationListComponent } from "../../components/publication-list/publication-list.component";
|
||||||
import { Publication } from "../../core/rest-services/publications/model/publication";
|
import { Publication } from "../../core/rest-services/publications/model/publication";
|
||||||
import { CommonModule } from "@angular/common";
|
import { CommonModule } from "@angular/common";
|
||||||
import { RouterModule } from "@angular/router";
|
import { RouterModule } from "@angular/router";
|
||||||
import { MatTooltipModule } from "@angular/material/tooltip";
|
import { MatTooltipModule } from "@angular/material/tooltip";
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-my-component',
|
selector: 'app-my-component',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
@@ -23,22 +22,28 @@ import { MatTooltipModule } from "@angular/material/tooltip";
|
|||||||
],
|
],
|
||||||
providers: [MyPublicationsService]
|
providers: [MyPublicationsService]
|
||||||
})
|
})
|
||||||
export class MyPublicationsComponent implements OnInit {
|
export class MyPublicationsComponent implements OnInit, OnDestroy {
|
||||||
private readonly myPublicationsService = inject(MyPublicationsService);
|
private readonly myPublicationsService = inject(MyPublicationsService);
|
||||||
|
private isLoadingSubscription?: Subscription;
|
||||||
|
private isLoadedSubscription?: Subscription;
|
||||||
|
|
||||||
|
isLoading = signal(false);
|
||||||
|
isLoaded = signal(false);
|
||||||
|
|
||||||
get publications$(): Observable<Publication[]> {
|
get publications$(): Observable<Publication[]> {
|
||||||
return this.myPublicationsService.publications$;
|
return this.myPublicationsService.publications$;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isLoading$(): Observable<boolean> {
|
|
||||||
return this.myPublicationsService.isLoading$;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isLoaded$(): Observable<boolean> {
|
|
||||||
return this.myPublicationsService.isLoaded$;
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.isLoadingSubscription = this.myPublicationsService.isLoading$
|
||||||
|
.subscribe(isLoading => this.isLoading.set(isLoading));
|
||||||
|
this.isLoadedSubscription = this.myPublicationsService.isLoaded$
|
||||||
|
.subscribe(isLoaded => this.isLoaded.set(isLoaded));
|
||||||
this.myPublicationsService.loadAuthenticatedUserPublications();
|
this.myPublicationsService.loadAuthenticatedUserPublications();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.isLoadingSubscription?.unsubscribe();
|
||||||
|
this.isLoadedSubscription?.unsubscribe();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
<app-publication-edition
|
@if (publicationIsNotNull()) {
|
||||||
title="Creation of a new publication"
|
<app-publication-edition
|
||||||
[publication]="publication"
|
title="Creation of a new publication"
|
||||||
[isSaving$]="isSaving$"
|
[publication]="publication()!!"
|
||||||
(publicationSave)="onPublicationSave($event)"
|
[isSaving$]="isSaving$"
|
||||||
i18n-title>
|
(publicationSave)="onPublicationSave($event)"
|
||||||
</app-publication-edition>
|
i18n-title>
|
||||||
|
</app-publication-edition>
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, inject, OnInit } from "@angular/core";
|
import { Component, computed, inject, OnInit, signal } from "@angular/core";
|
||||||
import { PublicationEditionComponent } from "../../components/publication-edition/publication-edition.component";
|
import { PublicationEditionComponent } from "../../components/publication-edition/publication-edition.component";
|
||||||
import { PublicationRestService } from "../../core/rest-services/publications/publication.rest-service";
|
import { PublicationRestService } from "../../core/rest-services/publications/publication.rest-service";
|
||||||
import { ActivatedRoute, Router } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
@@ -26,7 +26,8 @@ export class PublicationCreationComponent implements OnInit {
|
|||||||
private readonly snackBar = inject(MatSnackBar);
|
private readonly snackBar = inject(MatSnackBar);
|
||||||
private isSavingSubject = new BehaviorSubject<boolean>(false);
|
private isSavingSubject = new BehaviorSubject<boolean>(false);
|
||||||
private subscriptions: Subscription[] = [];
|
private subscriptions: Subscription[] = [];
|
||||||
publication!: Publication;
|
publication = signal<Publication | undefined>(undefined);
|
||||||
|
publicationIsNotNull = computed(() => !!this.publication());
|
||||||
|
|
||||||
get isSaving$(): Observable<boolean> {
|
get isSaving$(): Observable<boolean> {
|
||||||
return this.isSavingSubject.asObservable();
|
return this.isSavingSubject.asObservable();
|
||||||
@@ -40,7 +41,7 @@ export class PublicationCreationComponent implements OnInit {
|
|||||||
name: authenticatedUser.pseudo,
|
name: authenticatedUser.pseudo,
|
||||||
image: authenticatedUser.photoId ?? ''
|
image: authenticatedUser.photoId ?? ''
|
||||||
};
|
};
|
||||||
this.publication = {
|
const newPublication: Publication = {
|
||||||
id: '',
|
id: '',
|
||||||
key: '',
|
key: '',
|
||||||
title: '',
|
title: '',
|
||||||
@@ -52,6 +53,7 @@ export class PublicationCreationComponent implements OnInit {
|
|||||||
categoryId: '',
|
categoryId: '',
|
||||||
author
|
author
|
||||||
};
|
};
|
||||||
|
this.publication.set(newPublication);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@if ((isLoading$ | async) == true) {
|
@if (isLoading()) {
|
||||||
<h2 i18n>Loading publication to edit...</h2>
|
<h2 i18n>Loading publication to edit...</h2>
|
||||||
<mat-spinner></mat-spinner>
|
<mat-spinner></mat-spinner>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { CommonModule, Location } from '@angular/common';
|
import { CommonModule, Location } from '@angular/common';
|
||||||
import { Component, inject, OnDestroy, OnInit } from '@angular/core';
|
import { Component, inject, OnDestroy, OnInit, signal } from '@angular/core';
|
||||||
import { ReactiveFormsModule } from '@angular/forms';
|
import { ReactiveFormsModule } from '@angular/forms';
|
||||||
import { MatDialogModule } from '@angular/material/dialog';
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
@@ -41,21 +41,17 @@ export class PublicationUpdateComponent implements OnInit, OnDestroy {
|
|||||||
private readonly location = inject(Location);
|
private readonly location = inject(Location);
|
||||||
private readonly router = inject(Router);
|
private readonly router = inject(Router);
|
||||||
private readonly snackBar = inject(MatSnackBar);
|
private readonly snackBar = inject(MatSnackBar);
|
||||||
private isLoadingSubject = new BehaviorSubject<boolean>(false);
|
|
||||||
private isSavingSubject = new BehaviorSubject<boolean>(false);
|
private isSavingSubject = new BehaviorSubject<boolean>(false);
|
||||||
private subscriptions: Subscription[] = [];
|
private subscriptions: Subscription[] = [];
|
||||||
|
isLoading = signal(false);
|
||||||
publication: Publication | undefined;
|
publication: Publication | undefined;
|
||||||
|
|
||||||
get isLoading$(): Observable<boolean> {
|
|
||||||
return this.isLoadingSubject.asObservable();
|
|
||||||
}
|
|
||||||
|
|
||||||
get isSaving$(): Observable<boolean> {
|
get isSaving$(): Observable<boolean> {
|
||||||
return this.isSavingSubject.asObservable();
|
return this.isSavingSubject.asObservable();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.isLoadingSubject.next(true);
|
this.isLoading.set(true);
|
||||||
this.activatedRoute.paramMap.subscribe(params => {
|
this.activatedRoute.paramMap.subscribe(params => {
|
||||||
const publicationId = params.get('publicationId');
|
const publicationId = params.get('publicationId');
|
||||||
if (publicationId == undefined) {
|
if (publicationId == undefined) {
|
||||||
@@ -71,7 +67,7 @@ export class PublicationUpdateComponent implements OnInit, OnDestroy {
|
|||||||
this.snackBar.open(errorMessage, $localize`Close`, { duration: 5000 });
|
this.snackBar.open(errorMessage, $localize`Close`, { duration: 5000 });
|
||||||
console.error(errorMessage, error)
|
console.error(errorMessage, error)
|
||||||
})
|
})
|
||||||
.finally(() => this.isLoadingSubject.next(false));
|
.finally(() => this.isLoading.set(false));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
@if (isLoading) {
|
@if (isLoading()) {
|
||||||
<h2 i18n>Publication content loading...</h2>
|
<h2 i18n>Publication content loading...</h2>
|
||||||
<mat-spinner></mat-spinner>
|
<mat-spinner></mat-spinner>
|
||||||
} @else {
|
} @else {
|
||||||
@if (publication) {
|
@if (isPublicationUndefined()) {
|
||||||
|
<div class="loading-failed">
|
||||||
|
<h1 i18n>Publication failed to load...</h1>
|
||||||
|
</div>
|
||||||
|
} @else {
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<img src="/api/pictures/{{ publication.illustrationId }}" />
|
<img src="/api/pictures/{{ publication()?.illustrationId }}" />
|
||||||
<header>
|
<header>
|
||||||
<h1>{{ publication.title }}</h1>
|
<h1>{{ publication()?.title }}</h1>
|
||||||
<h2>{{ publication.description }}</h2>
|
<h2>{{ publication()?.description }}</h2>
|
||||||
@if (isAuthorAndUserEquals) {
|
@if (isAuthorAndUserEquals()) {
|
||||||
<a [routerLink]="['edit']"
|
<a [routerLink]="['edit']"
|
||||||
class="button action"
|
class="button action"
|
||||||
matTooltip="Click to edit the publication"
|
matTooltip="Click to edit the publication"
|
||||||
@@ -17,18 +21,18 @@
|
|||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
</header>
|
</header>
|
||||||
<main [innerHTML]="publication.parsedText"></main>
|
<main [innerHTML]="publication()?.parsedText"></main>
|
||||||
<footer>
|
<footer>
|
||||||
<div class="metadata">
|
<div class="metadata">
|
||||||
<img src="/api/pictures/{{ publication.author.image }}" [matTooltip]="publication.author.name" />
|
<img src="/api/pictures/{{ publication()?.author?.image }}" [matTooltip]="publication()?.author?.name" />
|
||||||
<div class="posting-data">
|
<div class="posting-data">
|
||||||
<span i18n>Publication posted by {{ publication.author.name }}</span>
|
<span i18n>Publication posted by {{ publication()?.author?.name }}</span>
|
||||||
<span class="publication-date">
|
<span class="publication-date">
|
||||||
({{ publication.creationDate | date: 'short' }})
|
({{ publication()?.creationDate | date: 'short' }})
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (isAuthorAndUserEquals) {
|
@if (isAuthorAndUserEquals()) {
|
||||||
<button type="button"
|
<button type="button"
|
||||||
(click)="deletePublication()"
|
(click)="deletePublication()"
|
||||||
matTooltip="Click to delete the publication"
|
matTooltip="Click to delete the publication"
|
||||||
@@ -40,9 +44,5 @@
|
|||||||
}
|
}
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
} @else {
|
|
||||||
<div class="loading-failed">
|
|
||||||
<h1 i18n>Publication failed to load...</h1>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,8 @@ $cardBorderRadius: .5em;
|
|||||||
|
|
||||||
:host {
|
:host {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, OnDestroy, OnInit, inject } from '@angular/core';
|
import { Component, OnDestroy, OnInit, computed, inject, signal } from '@angular/core';
|
||||||
import { PublicationRestService } from '../../core/rest-services/publications/publication.rest-service';
|
import { PublicationRestService } from '../../core/rest-services/publications/publication.rest-service';
|
||||||
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
@@ -30,9 +30,10 @@ export class PublicationComponent implements OnInit, OnDestroy {
|
|||||||
private readonly snackBar = inject(MatSnackBar);
|
private readonly snackBar = inject(MatSnackBar);
|
||||||
private paramMapSubscription?: Subscription;
|
private paramMapSubscription?: Subscription;
|
||||||
private afterDialogCloseSubscription?: Subscription;
|
private afterDialogCloseSubscription?: Subscription;
|
||||||
isLoading: boolean = false;
|
isLoading = signal(false);
|
||||||
isAuthorAndUserEquals: boolean = false;
|
isAuthorAndUserEquals = computed(() => this.authenticationService.getAuthenticatedUser()?.id === this.publication()?.author.id);
|
||||||
publication?: Publication;
|
publication = signal<Publication | undefined>(undefined);
|
||||||
|
isPublicationUndefined = computed(() => !this.publication())
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.paramMapSubscription = this.activatedRoute
|
this.paramMapSubscription = this.activatedRoute
|
||||||
@@ -41,12 +42,11 @@ export class PublicationComponent implements OnInit, OnDestroy {
|
|||||||
const publicationId = params.get('publicationId');
|
const publicationId = params.get('publicationId');
|
||||||
|
|
||||||
if (publicationId) {
|
if (publicationId) {
|
||||||
this.isLoading = true;
|
this.isLoading.set(true);
|
||||||
|
|
||||||
this.publicationRestService.getById(publicationId)
|
this.publicationRestService.getById(publicationId)
|
||||||
.then(publication => {
|
.then(publication => {
|
||||||
this.publication = publication;
|
this.publication.set(publication);
|
||||||
this.isAuthorAndUserEquals = this.authenticationService.getAuthenticatedUser()?.id === this.publication.author.id;
|
|
||||||
setTimeout(() => Prism.highlightAll(), 100);
|
setTimeout(() => Prism.highlightAll(), 100);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
@@ -55,7 +55,7 @@ export class PublicationComponent implements OnInit, OnDestroy {
|
|||||||
console.error(errorMessage, error);
|
console.error(errorMessage, error);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.isLoading = false;
|
this.isLoading.set(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -79,8 +79,8 @@ export class PublicationComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
this.afterDialogCloseSubscription = dialogRef.afterClosed()
|
this.afterDialogCloseSubscription = dialogRef.afterClosed()
|
||||||
.subscribe(response => {
|
.subscribe(response => {
|
||||||
if (response && this.publication?.id) {
|
if (response && !this.isPublicationUndefined()) {
|
||||||
this.publicationRestService.delete(this.publication.id);
|
this.publicationRestService.delete(this.publication()?.id!!);
|
||||||
this.snackBar.open($localize`Publication deleted`, $localize`Close`, { duration: 5000 });
|
this.snackBar.open($localize`Publication deleted`, $localize`Close`, { duration: 5000 });
|
||||||
this.location.back();
|
this.location.back();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user