improve profile reactiveness and action page performance

This commit is contained in:
Peter Maquiran
2024-07-24 13:37:02 +01:00
parent 717968ac52
commit 46bb078dd2
45 changed files with 543 additions and 247 deletions
+8 -1
View File
@@ -100,6 +100,10 @@ import { environment } from 'src/environments/environment';
import { StoreModule } from '@ngrx/store';
import { calendarReducer } from './services/Repositorys/Agenda/agenda-memory-source.service';
import {MatMenuModule} from '@angular/material/menu';
import {MatIconModule} from '@angular/material/icon';
// import { ServiceWorkerModule } from '@angular/service-worker';
// import { AngularFireModule } from '@angular/fire';
// import { AngularFireMessagingModule } from '@angular/fire/messaging';
@@ -184,7 +188,10 @@ import { FirebaseX } from '@ionic-native/firebase-x/ngx'; */
// options
DeplomaOptionsPageModule,
CreateProcessPageModule,
ImageCropperModule
ImageCropperModule,
MatMenuModule,
MatIconModule,
],
entryComponents: [
DiplomaOptionsPage,
+5 -5
View File
@@ -362,7 +362,7 @@ const routes: Routes = [
},
],
canActivate: [AuthGuard]
// canActivate: [AuthGuard]
},
{
@@ -376,7 +376,7 @@ const routes: Routes = [
},
],
canActivate: [AuthGuard]
// canActivate: [AuthGuard]
},
{
path: 'inactivity',
@@ -386,7 +386,7 @@ const routes: Routes = [
loadChildren: ()=> import('../pages/inactivity/inactivity.module').then(m => m.InactivityPageModule)
},
],
canActivate: [InactivityGuard]
// canActivate: [InactivityGuard]
},
{
path: 'login',
@@ -396,7 +396,7 @@ const routes: Routes = [
loadChildren: ()=> import('../pages/inactivity/inactivity.module').then(m => m.InactivityPageModule)
},
],
canActivate: [InactivityGuard]
// canActivate: [InactivityGuard]
},
{
path: 'pin',
@@ -406,7 +406,7 @@ const routes: Routes = [
loadChildren: ()=> import('../pages/inactivity/inactivity.module').then(m => m.InactivityPageModule)
},
],
canActivate: [InactivityGuard]
// canActivate: [InactivityGuard]
},
+8
View File
@@ -0,0 +1,8 @@
import { CameraSource } from "@capacitor/camera";
export interface ITakePictureParams {
width?: number,
height?: number,
quality: number,
source: CameraSource
}
+38
View File
@@ -0,0 +1,38 @@
import { Injectable } from '@angular/core';
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
import { ITakePictureParams } from './adaptor';
import { err, ok, Result } from 'neverthrow';
import { TracingType, XTracerAsync } from 'src/app/services/monitoring/opentelemetry/tracer';
import { error } from '../../services/Either/index';
@Injectable({
providedIn: 'root'
})
export class CameraService {
constructor() { }
async takePicture(data: ITakePictureParams, tracing?: TracingType): Promise<Result<string, any>> {
try {
tracing?.addEvent('take picture')
const capturedImage = await Camera.getPhoto({
...data,
// allowEditing: true,
resultType: CameraResultType.Base64,
});
tracing?.addEvent('end take picture')
tracing.log('image log', {
base64: capturedImage.base64String
})
return ok(capturedImage.base64String)
} catch(error) {
tracing?.log("camera error", {
error
})
tracing?.hasError('capture image')
return err(error)
}
}
}
+15 -15
View File
@@ -114,23 +114,23 @@ export class TokenInterceptor implements HttpInterceptor {
}),
catchError((error) => {
console.log(error)
SessionStore.user.Authorization = SessionStore.user.Authorization;
SessionStore.user.RefreshToken = SessionStore.user.RefreshToken;
SessionStore.setInativity(false)
/* SessionStore.setUrlBeforeInactivity(this.router.url); */
// SessionStore.user.Authorization = SessionStore.user.Authorization;
// SessionStore.user.RefreshToken = SessionStore.user.RefreshToken;
// SessionStore.setInativity(false)
// /* SessionStore.setUrlBeforeInactivity(this.router.url); */
if (environment.production) {
window.location.pathname = '/auth'
} else {
const pathBeforeGoOut = window.location.pathname
console.log('Before auth',window.location.pathname)
this.router.navigateByUrl('/auth', { replaceUrl: true }).then(() =>{
if(pathBeforeGoOut != "/auth") {
this.httpErrorHandle.httpsSucessMessagge('sessonExpired')
}
// if (environment.production) {
// window.location.pathname = '/auth'
// } else {
// const pathBeforeGoOut = window.location.pathname
// console.log('Before auth',window.location.pathname)
// this.router.navigateByUrl('/auth', { replaceUrl: true }).then(() =>{
// if(pathBeforeGoOut != "/auth") {
// this.httpErrorHandle.httpsSucessMessagge('sessonExpired')
// }
})
}
// })
// }
return of(false);
})
);
@@ -365,7 +365,6 @@ export class DocumentSetUpMeetingPage implements OnInit {
if(!isHttpError(value.error)) {
this.toastService._badRequest('Pedimos desculpa mas não foi possível executar a acção. Por favor, contacte o apoio técnico. #1')
console.log(value.error)
tracing.bugFlag()
} else {
this.httpErroHandle.httpStatusHandle(value.error.status)
}
@@ -379,7 +378,6 @@ export class DocumentSetUpMeetingPage implements OnInit {
tracing.setAttribute('outcome', 'failed')
tracing.setAttribute('no', 'this.selectedUserCalendar')
tracing.bugFlag()
}
laoder.remove();
@@ -8,12 +8,26 @@ import { EditProfilePageRoutingModule } from './edit-profile-routing.module';
import { EditProfilePage } from './edit-profile.page';
import {MatMenuModule} from '@angular/material/menu';
import {MatButtonModule} from '@angular/material/button';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import {MatIconModule} from '@angular/material/icon';
@NgModule({
imports: [
CommonModule,
FormsModule,
IonicModule,
EditProfilePageRoutingModule
EditProfilePageRoutingModule,
FontAwesomeModule,
MatMenuModule,
MatButtonModule,
MatIconModule,
],
declarations: [EditProfilePage]
})
@@ -58,39 +58,46 @@
</div>
<div>
<div class="d-flex align-center flex-column" (click)="takePicture()">
<div class="d-flex justify-center align-center " >
<div mat-icon-button [matMenuTriggerFor]="menu" aria-label="Example icon-button with a menu">
<div class="d-flex align-center flex-column" >
<div *ngIf="profilePicture == '' ">
<img
<div *ngIf="profilePictureSubject == undefined ">
<img
class="profile-pic" src="assets/images/theme/gov/icons-profile.svg">
class="profile-pic" src="assets/images/theme/gov/icons-profile.svg">
<!-- <img *ngIf="SessionStore.user.RoleDescription == 'Presidente da República' " class="profile-pic"
src='assets/images/presidente.png'>
<img *ngIf="SessionStore.user.RoleDescription == 'Ministro e Director do Gabinete do PR' "
class="profile-pic" src='assets/images/ministro.png'>
<img *ngIf="SessionStore.user.RoleDescription == 'Secretário Geral' " class="profile-pic"
src='assets/images/secretaria_geral.png'> -->
</div>
<div *ngIf="profilePicture != '' ">
<img class="profile-pic" src={{profilePicture}}>
</div>
<!-- <ion-icon *ngIf="ThemeService.currentTheme == 'default' " class="profile-pic"
src="assets/images/icons-default-profile.svg"></ion-icon>
<ion-icon *ngIf="ThemeService.currentTheme == 'gov' " class="profile-pic"
src="assets/images/theme/gov/icons-profile.svg"></ion-icon>
<ion-icon *ngIf="ThemeService.currentTheme == 'doneIt' " class="profile-pic"
src="assets/images/theme/{{ThemeService.currentTheme}}/icons-profile.svg"></ion-icon> -->
<div
style="background: black;width: 50px;height: 50px;position: relative;top: -33px;left: 38px;border-radius: 55px;overflow: hidden;border: 5px solid white;">
<img src="assets/images/camera.png">
<!-- <img *ngIf="SessionStore.user.RoleDescription == 'Presidente da República' " class="profile-pic"
src='assets/images/presidente.png'>
<img *ngIf="SessionStore.user.RoleDescription == 'Ministro e Director do Gabinete do PR' "
class="profile-pic" src='assets/images/ministro.png'>
<img *ngIf="SessionStore.user.RoleDescription == 'Secretário Geral' " class="profile-pic"
src='assets/images/secretaria_geral.png'> -->
</div>
<div *ngIf="(profilePictureSubject | async) as calendarData">
<img class="profile-pic" src={{calendarData.base64}}>
</div>
<div
*ngIf="camecaIcons"
style="background: black;width: 50px;height: 50px;position: relative;margin-top: -33px;left: 38px;border-radius: 55px;overflow: hidden;border: 5px solid white;">
<img src="assets/images/camera.png">
</div>
</div>
</div>
<mat-menu #menu="matMenu">
<button (click)="uploadPicture(CameraSource.Camera)" mat-menu-item>
<span>take picture</span>
</button>
<button (click)="uploadPicture(CameraSource.Photos)" mat-menu-item >
<span>Ficheiro</span>
</button>
</mat-menu>
</div>
</div>
<div class="profile-info">
@@ -6,12 +6,16 @@ import { SessionStore } from 'src/app/store/session.service';
import { environment } from 'src/environments/environment';
import { BackgroundService } from 'src/app/services/background.service';
import { ThemeService } from 'src/app/services/theme.service';
import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera';
import { Filesystem, Directory } from '@capacitor/filesystem';
import { File } from '@awesome-cordova-plugins/file/ngx';
import { CameraSource } from '@capacitor/camera';
import { StorageService } from 'src/app/services/storage.service';
import { AttachmentsService } from 'src/app/services/attachments.service';
import { CameraService } from 'src/app/infra/camera/camera.service';
import { ToastService } from 'src/app/services/toast.service';
import { HttpErrorHandle } from 'src/app/services/http-error-handle.service';
import { TracingType, XTracerAsync } from 'src/app/services/monitoring/opentelemetry/tracer';
import { UserRepositoryService } from 'src/app/module/user/data/user-repository.service';
import { isHttpError } from 'src/app/services/http.service';
import { UserProfilePicture } from 'src/app/module/user/data/datasource/user-local-repository.service';
import { Observable } from 'rxjs';
@Component({
selector: 'app-edit-profile',
@@ -27,19 +31,28 @@ export class EditProfilePage implements OnInit {
capturedImageTitle = '';
profilePicture = "";
profilePictureSubject: Observable<UserProfilePicture>
camecaIcons = false
constructor(private modalController: ModalController,
private animationController: AnimationController,
public platform: Platform,
private BackgroundService: BackgroundService,
public ThemeService: ThemeService,
private file: File,
private storageService: StorageService,
private attachmentService: AttachmentsService
private CameraService: CameraService,
private toastService: ToastService,
private httpErrorHandle: HttpErrorHandle,
private UserRepositoryService: UserRepositoryService
) { }
) {
this.profilePictureSubject = this.UserRepositoryService.getProfilePictureLive() as any
}
ngOnInit() {
setTimeout(() => {
this.camecaIcons = true
}, 100)
this.getProfilpictureFromStorage();
}
getProfilpictureFromStorage() {
@@ -54,27 +67,7 @@ export class EditProfilePage implements OnInit {
this.profilePicture = "";
})
}
/* getProfilpicture(guid) {
console.log('Get picture ', guid.path)
this.attachmentService.getUserProfilePhoto().subscribe(async (picture: any) => {
console.log('Get picture ', picture)
this.storageService.store(this.SessionStore.user.RoleID.toString() + "guid", guid.path)
this.storageService.store(this.SessionStore.user.RoleID.toString(), picture).then((value) => {
this.profilePicture = picture
this.SessionStore.user.UserPhoto = picture;
console.log('picture saved')
}).catch((error) => {
console.log('picture not saved')
});
}, ((error) => {
console.log('Error get profile picture: ', error)
}))
} */
close() {
this.modalController.dismiss();
@@ -171,48 +164,65 @@ export class EditProfilePage implements OnInit {
this.BackgroundService.paint();
}
CameraSource = CameraSource
@XTracerAsync({name:'edit-profile/takePicture', bugPrint: true, autoFinish: false})
async uploadPicture(source: CameraSource, tracing?: TracingType) {
async takePicture() {
const capturedImage = await Camera.getPhoto({
const capturedImage = await this.CameraService.takePicture({
width: 250,
height: 250,
quality: 100,
// allowEditing: true,
resultType: CameraResultType.Base64,
source: CameraSource.Camera
});
source: source
}, tracing)
this.capturedImage = capturedImage.base64String;
var object = JSON.stringify({
"ImageBase64": this.capturedImage
if(capturedImage.isOk()) {
this.capturedImage = capturedImage.value;
var object = {
"ImageBase64": this.capturedImage
}
tracing.addEvent('serialize image')
const guid = await this.UserRepositoryService.addUserProfilePhoto(object)
if(guid.isOk()) {
tracing.addEvent('upload image')
const base = await this.UserRepositoryService.getUserProfilePhoto(guid.value, tracing)
if(base.isOk()) {
tracing.addEvent('download image')
this.storageService.store(this.SessionStore.user.RoleID.toString(), 'data:image/jpeg;base64,'+base.value).then((value) => {
tracing.addEvent('store image in')
this.profilePicture = 'data:image/jpeg;base64,' + base.value;
tracing.setAttribute("picture.save", "true")
tracing.finish()
}).catch((error) => {
tracing.setAttribute("picture.save", "false")
tracing.finish()
});
} else {
if(!isHttpError(base.error)) {
this.toastService._badRequest('Pedimos desculpa mas não foi possível executar a acção. Por favor, contacte o apoio técnico.')
} else {
this.httpErrorHandle.httpStatusHandle(base.error)
}
tracing.finish()
}
} else {
if(!isHttpError(guid.error)) {
this.toastService._badRequest('Pedimos desculpa mas não foi possível executar a acção. Por favor, contacte o apoio técnico.')
} else {
this.httpErrorHandle.httpStatusHandle(guid.error)
}
tracing.finish()
}
}
)
console.log('ATTACHME ', object)
this.attachmentService.addUserProfilePhoto(object).subscribe((guid) => {
console.log('GUID ', guid)
console.log(this.SessionStore.user.RoleID.toString())
//get user profile picture base64
this.attachmentService.getUserProfilePhoto(guid).subscribe((base) => {
console.log('before picture saved',base)
this.storageService.store(this.SessionStore.user.RoleID.toString(), 'data:image/jpeg;base64,'+base).then((value) => {
this.profilePicture = 'data:image/jpeg;base64,' + base;
console.log('picture saved',value)
}).catch((error) => {
console.log('picture not saved')
});
},(error) => {
console.log('profile picture erro: ', error)
})
/* this.getProfilpicture(guid); */
}, ((error) => {
console.log('Erro Upload profile picture ', error)
}))
}
@@ -268,4 +278,3 @@ export class EditProfilePage implements OnInit {
}
}
+3 -3
View File
@@ -39,7 +39,7 @@
<div class="d-flex align-center flex-column">
<div *ngIf="profilePicture == ''">
<div *ngIf="profilePictureSubject == undefined ">
<img
class="profile-pic" src="assets/images/theme/gov/icons-profile.svg">
@@ -51,9 +51,9 @@
src='assets/images/secretaria_geral.png'> -->
</div>
<div *ngIf="profilePicture != ''" class="profile-pic">
<div *ngIf="(profilePictureSubject | async) as calendarData"class="profile-pic">
<img class="profile-pic"
src={{profilePicture}}>
src={{calendarData.base64}}>
</div>
+7 -2
View File
@@ -17,6 +17,9 @@ import { NotificationRepositoryService } from 'src/app/module/notification/data/
import { Observable } from 'rxjs';
import { NotificationTable } from 'src/app/module/notification/data/infra/db/notification.db';
import { isHttpError } from 'src/app/services/http.service';
import { UserRepositoryService } from 'src/app/module/user/data/user-repository.service';
import { UserProfilePicture } from 'src/app/module/user/data/datasource/user-local-repository.service';
@Component({
selector: 'app-profile',
templateUrl: './profile.page.html',
@@ -50,6 +53,7 @@ export class ProfilePage implements OnInit {
notificationList$: Observable<NotificationTable[]>
objectRead = {}
profilePictureSubject: Observable<UserProfilePicture>
constructor(
private modalController: ModalController,
@@ -64,10 +68,11 @@ export class ProfilePage implements OnInit {
private authservice: AuthService,
private agendaDataRepository: AgendaDataRepositoryService,
private toastService: ToastService,
private notificationRepositoryService: NotificationRepositoryService
private notificationRepositoryService: NotificationRepositoryService,
private UserRepositoryService: UserRepositoryService
) {
this.profilePictureSubject = this.UserRepositoryService.getProfilePictureLive() as any
window['e'] = () => {
console.log(
this.zone.run(() => this.router.navigate(['/home/chat']))
@@ -137,7 +137,6 @@ export class ViewEventPage implements OnInit {
} else {
tracing.setAttribute('eventId', this.eventId)
tracing.setAttribute('outcome', 'failed')
tracing.bugFlag()
if(!isHttpError(res.error)) {
this.toastService._badRequest('Pedimos desculpa mas não foi possível executar a acção. Por favor, contacte o apoio técnico. #13')
@@ -0,0 +1,52 @@
import { Injectable } from '@angular/core';
import { z } from 'zod';
import { Dexie, EntityTable, liveQuery } from 'Dexie';
const UserProfilePicture = z.object({
base64: z.string(),
id: z.number().optional(),
})
const UserTable = z.object({
ChunksBase64: z.number(),
});
export type UserProfilePicture = z.infer<typeof UserProfilePicture>
// Database declaration (move this to its own module also)
const session = new Dexie('session') as Dexie & {
profilePicture: EntityTable<UserProfilePicture, 'id'>;
};
session.version(1).stores({
profilePicture: '++id, base64'
});
@Injectable({
providedIn: 'root'
})
export class UserLocalRepositoryService {
constructor() { }
async addProfilePicture(data: UserProfilePicture) {
try {
await session.transaction('rw', session.profilePicture, async () => {
// Clear existing records from myTable
await session.profilePicture.clear();
await session.profilePicture.add(data);
});
console.log('Clear and add operations completed within transaction.');
} catch (error) {
console.error('Error performing transaction:', error, data);
}
}
getLiveProfilePicture() {
return liveQuery(() =>
session.profilePicture.orderBy('id').reverse().first()
)
}
}
@@ -0,0 +1,9 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class UserMemoryRepositoryService {
constructor() { }
}
@@ -0,0 +1,39 @@
import { Injectable } from '@angular/core';
import { HttpService } from 'src/app/services/http.service';
import { environment } from 'src/environments/environment';
import { IProfilePictureInputDTO } from '../dto/profilePictureInputDTO';
import { HttpHeaders } from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class UserRemoteRepositoryService {
constructor(
private httpService: HttpService
) { }
getUserProfilePhoto(guid: string) {
const geturl = environment.apiURL + 'UserAuthentication/GetPhoto';
const params = {
UserPhoto: guid
}
return this.httpService.get<string>(`${geturl}`, params);
}
addUserProfilePhoto(data: IProfilePictureInputDTO) {
const geturl = environment.apiURL + 'UserAuthentication/AddPhoto';
let http = new HttpHeaders();
http = http.set('content-type', "application/json");
http = http.set('accept', "application/json");
let options = {
headers: http
};
return this.httpService.post<string>(`${geturl}`, data, options);
}
}
@@ -0,0 +1,9 @@
import { z } from "zod";
const profilePictureInputDTOSchema = z.object({
ImageBase64: z.string()
})
// const profilePictureInputDTOSchema = z.string()
export type IProfilePictureInputDTO = z.infer<typeof profilePictureInputDTOSchema>
@@ -0,0 +1,47 @@
import { Injectable } from '@angular/core';
import { UserRemoteRepositoryService } from './datasource/user-remote-repository.service';
import { IProfilePictureInputDTO } from './dto/profilePictureInputDTO';
import { UserLocalRepositoryService } from './datasource/user-local-repository.service';
import { TracingType } from 'src/app/services/monitoring/opentelemetry/tracer';
@Injectable({
providedIn: 'root'
})
export class UserRepositoryService {
constructor(
private remote: UserRemoteRepositoryService,
private local: UserLocalRepositoryService
) { }
async getUserProfilePhoto(guid: string , tracing?: TracingType) {
const result = await this.remote.getUserProfilePhoto(guid)
if(result.isOk()) {
this.local.addProfilePicture({base64: 'data:image/jpeg;base64,' + result.value})
} else {
tracing?.setAttribute("picture.upload", "false")
tracing?.hasError("cant upload picture")
}
return result
}
async addUserProfilePhoto(data: IProfilePictureInputDTO, tracing?: TracingType) {
const result = await this.remote.addUserProfilePhoto(data)
if(result.isOk()) {
this.local.addProfilePicture({base64: 'data:image/jpeg;base64,' + data.ImageBase64})
} else {
tracing?.setAttribute("picture.upload", "false")
tracing?.hasError("cant upload picture")
}
return result
}
getProfilePictureLive() {
return this.local.getLiveProfilePicture()
}
}
-2
View File
@@ -782,7 +782,6 @@ export class AgendaPage implements OnInit {
}
tracing.setAttribute('outcome', 'failed')
tracing.bugFlag()
load++
if (load == (selectedCalendarIds).length) {
@@ -801,7 +800,6 @@ export class AgendaPage implements OnInit {
tracing.setAttribute('outcome', 'failed')
tracing.setAttribute('error', 'selectedCalendar.wxUserId')
this.toastService._badRequest('Pedimos desculpa mas não foi possível executar a acção. Por favor, contacte o apoio técnico. #2')
tracing.bugFlag()
}
tracing.addEvent('load range end')
@@ -177,7 +177,6 @@ export class ViewEventPage implements OnInit {
tracing.setAttribute('outcome', 'failed')
tracing.bugFlag()
// this.toastService.badRequest('Este evento já não existe na sua agenda')
this.RouteService.goBack();
@@ -125,13 +125,11 @@ export class ApproveEventPage implements OnInit {
}
tracing.setAttribute('outcome', 'failed')
tracing.bugFlag()
this.httpErrorHandle.httpStatusHandle(res.error)
} else if(!isHttpError(res.error)) {
tracing.setAttribute('outcome', 'failed')
tracing.bugFlag()
this.toastService._badRequest('Pedimos desculpa mas não foi possível executar a acção. Por favor, contacte o apoio técnico. #12')
}
@@ -222,7 +222,7 @@ export class EventListPage implements OnInit {
} else {
tracing.setAttribute('outcome', 'failed')
tracing.bugFlag()
if(!isHttpError(allEvents.error)) {
this.toastService._badRequest('Pedimos desculpa mas não foi possível executar a acção. Por favor, contacte o apoio técnico. #4')
}
@@ -395,7 +395,6 @@ export class BookMeetingModalPage implements OnInit {
if(!isHttpError(value.error)) {
this.toastService._badRequest('Pedimos desculpa mas não foi possível executar a acção. Por favor, contacte o apoio técnico. #1')
console.log(value.error)
tracing.bugFlag()
} else {
this.httpErroHandle.httpStatusHandle(value.error.status)
}
@@ -409,7 +408,6 @@ export class BookMeetingModalPage implements OnInit {
tracing.setAttribute('outcome', 'failed')
tracing.setAttribute('no', 'this.selectedUserCalendar')
tracing.bugFlag()
}
if (this.task.FsId == '8') {
@@ -67,7 +67,7 @@
<p>mais {{ PublicationFromMvService.form.Files.length - displayLimit }}</p>
</ion-thumbnail>
<ion-label class="pl-10">
<p>{{capturedImageTitle}}</p>
<!-- <p>{{capturedImageTitle}}</p> -->
<p hidden>size</p>
</ion-label>
</div>
@@ -222,7 +222,7 @@ export class NewPublicationPage implements OnInit {
source: CameraSource.Camera
});
this.capturedImage = 'data:image/jpeg;base64,' + capturedImage.base64String;
this.capturedImageTitle = 'foto';
this.capturedImageTitle = '';
this.showCroppModal();
}
@@ -22,9 +22,9 @@
</div>
</div>
<div class="post-item overflow-y-auto">
<div class="post-item overflow-y-auto height-100 ">
<div style="width: 100%; height: 395px;">
<div style="width: 100%;">
<app-swiper
[publicationList]=publication
></app-swiper>
@@ -52,7 +52,6 @@ ion-toolbar {
.div-title {
/* padding: 0!important; */
float: left;
margin-top: 100px;
}
.post-img {
@@ -95,4 +94,4 @@ ion-toolbar {
.font-14-rem {
font-size: rem(14) !important;
}
}
@@ -23,6 +23,8 @@ import { PublicationHolderService } from 'src/app/services/publication/publicati
})
export class PublicationDetailPage implements OnInit {
@ViewChild('videoPlayer', { static: true }) videoPlayer: ElementRef;
@ViewChild('ScrollContainer', { static: true }) ScrollContainer: ElementRef;
showLoader: boolean;
DocumentId: string;
folderId: string;
@@ -82,8 +84,6 @@ export class PublicationDetailPage implements OnInit {
FileExtension: '',
};
window['publicationEdit'] = () => {
if(this.isComponentIsAlive == true) {
this.getPublicationDetail()
@@ -92,9 +92,10 @@ export class PublicationDetailPage implements OnInit {
}
OnDestroy() {
ngOnDestroy() {
this.isComponentIsAlive = false
}
doRefresh(event) {
this.getPublicationDetail();
@@ -43,7 +43,6 @@
<ion-card *ngFor="let publication of publicationFolderService.publicationList[folderId] let i = index">
<ion-card-content>
<div style="width: 100%; height: 395px;">
<app-swiper
[publicationList]=publication
+9 -5
View File
@@ -1,8 +1,14 @@
import { HttpClient, HttpErrorResponse, HttpResponse } from '@angular/common/http';
import { HttpClient, HttpErrorResponse, HttpHeaders, HttpResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { ok, err, Result } from 'neverthrow';
import { HttpParams } from '@angular/common/http';
import { TracingType } from './monitoring/opentelemetry/tracer';
export type HTTPOptions = {
headers?: HttpHeaders
}
@Injectable({
providedIn: 'root'
})
@@ -10,10 +16,10 @@ export class HttpService {
constructor(private http:HttpClient) { }
async post<T>(url: string, body: any, tracing?: TracingType): Promise<Result<T, HttpErrorResponse>> {
async post<T>(url: string, body: any, options:HTTPOptions ={}, tracing?: TracingType): Promise<Result<T, HttpErrorResponse>> {
try {
const result = await this.http.post(url, body).toPromise()
const result = await this.http.post(url, body, options).toPromise()
return ok (result as T)
} catch (e) {
return err(e as HttpErrorResponse)
@@ -42,11 +48,9 @@ export class HttpService {
if(isHttpError(e)) {
tracing?.setAttribute('status.code', e.status.toString())
if (e.status == 400) {
tracing?.bugFlag()
tracing?.setAttribute('outcome', 'failed')
}
} else {
tracing?.bugFlag()
tracing.setAttribute('outcome', 'failed')
tracing?.setAttribute('map.error', 'true')
tracing?.setAttribute('map.error.context', JSON.stringify(e))
@@ -84,4 +84,4 @@ function childSpanExample(parentContext) {
}, 500);
}
parentSpanExample()
// parentSpanExample()
@@ -8,7 +8,9 @@ import { environment } from 'src/environments/environment';
import { UseCaseCounter } from './matrix';
import { openTelemetryLogging } from './logging';
// import { context, propagation } from '@opentelemetry/api';
import {
SpanStatus, SpanStatusCode
} from '@opentelemetry/api';
const tracerInstance = OpentelemetryAgendaProvider.getTracer('example-tracer-hole', '111', {})
const tracerNotificationInstance = OpentelemetryNotificationProvider.getTracer('example-tracer-hole', '111', {})
// const logger: ILoggerAdapter = new ColoredLoggerService()
@@ -53,15 +55,16 @@ const createTracingInstance = ({bugPrint, name, module, autoFinish}): TracingTyp
const data = {
event: {},
tags: {}
tags: {},
status: {} as any,
}
return {
const returnObject = {
span: span as any,
tracer: tracerInstance,
tracerId: requestId,
attributes: SemanticAttributes,
setStatus: (status: any) => {
setStatus: (status: SpanStatus) => {
span.setStatus(status);
},
addEvent: (context: string, message?: any, obj?: any) => {
@@ -79,7 +82,7 @@ const createTracingInstance = ({bugPrint, name, module, autoFinish}): TracingTyp
span.setAttribute(key, value);
if(key =='outcome' && value == 'failed') {
span.setAttribute('error', 'true')
returnObject.hasError('error')
}
},
log(message: string, data: Object) {
@@ -110,18 +113,25 @@ const createTracingInstance = ({bugPrint, name, module, autoFinish}): TracingTyp
finish: () => {
if(environment.apiURL != 'https://gdqas-api.oapr.gov.ao/api/') {
span.end();
UseCaseCounter.add(1, {user: SessionStore?.user?.FullName, outcome:data.tags['outcome'], usecase: name})
UseCaseCounter.add(1, {user: SessionStore?.user?.FullName, outcome:data.tags['outcome'] || data.status?.code , usecase: name})
}
if(bugPrint && data.tags['outcome'] == 'failed') {
if(bugPrint && (data.tags['outcome'] == 'failed' || data.status?.code == SpanStatusCode.ERROR)) {
console.error(name, data)
}
},
bugFlag:() => {},
hasError:(message: string) => {
if(data.status?.code != SpanStatusCode.ERROR) {
data.status = {code: SpanStatusCode.ERROR, message}
span.setStatus({code: SpanStatusCode.ERROR, message})
span.setAttribute('outcome','failed')
}
},
createSpan: (name, parent?: any) => {
return tracerInstance.startSpan(name, { root: false }, parent) as Span;
}
}
return returnObject
}
export function XTracerAsync({ name, bugPrint, module = null, autoFinish = true, daley =0 }) {
@@ -221,7 +231,7 @@ export type TracingType = {
getAttribute: (key: string) => string;
LocalLogEvent: (name: string, attributesOrStartTime: any, obj?:any) => void;
finish: () => void;
bugFlag:() => void;
hasError:(message: string) => void;
createSpan:(name, parent?: any) => Span;
};
@@ -189,7 +189,6 @@ export class EditEventToApprovePage implements OnInit {
} else {
tracing.setAttribute('outcome', 'failed')
tracing.bugFlag()
this.httpErroHalde.httpStatusHandle(res.error)
}
@@ -160,7 +160,6 @@ export class EventListPage implements OnInit {
} else {
tracing.setAttribute('outcome', 'failed')
tracing.bugFlag()
if(!isHttpError(allEvents.error)) {
this.toastService._badRequest('Pedimos desculpa mas não foi possível executar a acção. Por favor, contacte o apoio técnico. #3')
@@ -200,7 +200,6 @@ export class ViewEventPage implements OnInit {
}
tracing.setAttribute('outcome', 'failed')
tracing.bugFlag()
this.viewEventDetailDismiss.emit({
type: 'close'
@@ -191,7 +191,6 @@ export class EditEventToApproveComponent implements OnInit {
tracing.setAttribute('outcome', 'success')
} else {
tracing.setAttribute('outcome', 'failed')
tracing.bugFlag()
this.httpErroHalde.httpStatusHandle(res.error)
}
@@ -233,7 +233,6 @@ export class EventsToApprovePage implements OnInit {
} else {
tracing.setAttribute('outcome', 'failed')
tracing.bugFlag()
if(!isHttpError(allEvents.error)) {
this.toastService._badRequest('Pedimos desculpa mas não foi possível executar a acção. Por favor, contacte o apoio técnico. #4')
+6 -6
View File
@@ -56,7 +56,7 @@
<ion-icon *ngIf="ThemeService.currentTheme == 'default' " class="icon font-45-em" src='assets/images/icons-profile.svg'></ion-icon>
<ion-icon *ngIf="ThemeService.currentTheme == 'gov' " class="icon font-45-em" src='assets/images/theme/gov/icons-profile.svg'></ion-icon> -->
<div (click)="openProfile()" *ngIf="profilePicture == ''" class="profile-image" >
<div (click)="openProfile()" *ngIf="profilePictureSubject == undefined " class="profile-image" >
<!-- <img *ngIf="loggeduser.RoleDescription == 'Presidente da República' " class="profile-image"
src='assets/images/presidente.png'>
<img *ngIf="loggeduser.RoleDescription == 'Ministro e Director do Gabinete do PR' " class="profile-image"
@@ -69,8 +69,8 @@
</div>
<div (click)="openProfile()" *ngIf="profilePicture != ''" class="profile-image">
<img class="profile-image image-prety" src={{profilePicture}}>
<div (click)="openProfile()" *ngIf="(profilePictureSubject | async) as calendarData" class="profile-image">
<img class="profile-image image-prety" src={{calendarData.base64}}>
</div>
@@ -226,7 +226,7 @@
<ion-icon *ngIf="ThemeService.currentTheme == 'doneIt' " class="icon" src='assets/images/theme/doneIt/icons-profile.svg'></ion-icon>
<ion-icon *ngIf="ThemeService.currentTheme == 'gov' " class="icon" src='assets/images/theme/gov/icons-profile.svg'></ion-icon> -->
<div *ngIf="profilePicture == ''" class="profile-image">
<div *ngIf="profilePictureSubject == undefined " class="profile-image">
<!-- <img *ngIf="loggeduser.RoleDescription == 'Presidente da República' " class="profile-image"
src='assets/images/presidente.png'>
<img *ngIf="loggeduser.RoleDescription == 'Ministro e Director do Gabinete do PR' " class="profile-image"
@@ -239,8 +239,8 @@
</div>
<div *ngIf="profilePicture != ''" class="profile-image">
<img class="profile-image font-45-em image-prety" src={{profilePicture}}>
<div *ngIf="(profilePictureSubject | async) as calendarData" class="profile-image">
<img class="profile-image font-45-em image-prety" src={{calendarData.base64}}>
</div>
<div class="profile-text" *ngIf="(notificationCount$ | async) as notificationCount ">
+7 -2
View File
@@ -19,6 +19,9 @@ import { PublicationHolderService } from 'src/app/services/publication/publicati
import { Cy } from 'cypress/enum'
import { NotificationRepositoryService } from 'src/app/module/notification/data/notification-repository.service';
import { Observable } from 'rxjs';
import { UserRepositoryService } from 'src/app/module/user/data/user-repository.service';
import { UserProfilePicture } from 'src/app/module/user/data/datasource/user-local-repository.service';
@Component({
selector: 'app-header',
templateUrl: './header.page.html',
@@ -57,6 +60,7 @@ export class HeaderPage implements OnInit {
Cy = Cy
notificationCount$: Observable<number>
profilePictureSubject: Observable<UserProfilePicture>
constructor(
private router: Router,
@@ -73,9 +77,10 @@ export class HeaderPage implements OnInit {
public NotificationHolderService: NotificationHolderService,
public HeaderSettingsService: HeaderSettingsService,
public PublicationHolderService: PublicationHolderService,
private notificationRepositoryService: NotificationRepositoryService
private notificationRepositoryService: NotificationRepositoryService,
private UserRepositoryService: UserRepositoryService
) {
this.profilePictureSubject = this.UserRepositoryService.getProfilePictureLive() as any
this.notificationCount$ = this.notificationRepositoryService.getNotificationLiveCount()
this.loggeduser = SessionStore.user;
@@ -24,7 +24,7 @@
</ion-header>
<ion-content>
<div class="content-container">
<div class="content-container" #ScrollContainer>
<div *ngIf="publication.Title != ''">
<ion-refresher name="refresher" slot="fixed" (ionRefresh)="doRefresh($event)">
<ion-progress-bar type="indeterminate" *ngIf="showLoader"></ion-progress-bar>
@@ -35,15 +35,20 @@
<swiper-container navigation="true" [pagination]="{clickable: true, dynamicBullets: true }">
<swiper-slide *ngFor="let files of publication.Files let k = index">
<!-- <div (click)="openPreview(publication)"> -->
<div>
<img *ngIf="checkFileType.checkFileType(files.FileExtension ) == 'image'" alt="image" tappable
src="{{'data:image/jpg;base64,' + files.FileBase64}}">
<div >
<img *ngIf="checkFileType.checkFileType(files.FileExtension ) == 'image' && files.FileBase64!=''" alt="sem image" tappable
src="{{'data:image/jpg;base64,' + files.FileBase64}}"
style="max-height:{{containerMaxHeight}}px"
>
<video *ngIf="checkFileType.checkFileType(files.FileExtension ) == 'video'" controls="controls" preload="metadata"
webkit-playsinline="webkit-playsinline">
<video *ngIf="checkFileType.checkFileType(files.FileExtension ) == 'video' && files.FileBase64!=''" controls="controls" preload="metadata"
webkit-playsinline="webkit-playsinline" style="max-height:{{containerMaxHeight}}px">
<source [src]="files.FileBase64" type="video/mp4">
</video>
<div *ngIf="files.FileBase64==''" style="height:{{containerMaxHeight}}px">
sem contiudo
</div>
</div>
</swiper-slide>
</swiper-container>
@@ -103,13 +103,7 @@ swiper-slide img {
display: block;
width: 100%;
height: 100%;
max-height: 1000px;
max-width: 868px;
min-height: 350px;
min-width: 468px;
object-fit: cover;
object-fit: contain;
text-align: center;
display: flex;
justify-content: center;
@@ -121,15 +115,18 @@ swiper-slide video {
width: 100%;
height: 100%;
max-height: 1000px;
max-width: 868px;
min-height: 350px;
min-width: 468px;
object-fit: cover;
object-fit: contain;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
}
swiper-slide {
margin-bottom: -100%;
}
.swiper-slide-active {
margin-bottom: 0px !important;
}
@@ -1,4 +1,4 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { Component, OnInit, Input, Output, EventEmitter, ViewChild, ElementRef } from '@angular/core';
import { ModalController } from '@ionic/angular';
import { Publication } from 'src/app/models/publication';
import { NewPublicationPage } from 'src/app/shared/publication/new-publication/new-publication.page';
@@ -29,6 +29,9 @@ export class PublicationDetailPage implements OnInit {
@Output() goBackToViewPublications = new EventEmitter();
publicationPipe = new PublicationPipe()
containerMaxWidth = 0
containerMaxHeight = 0
constructor(
private modalController: ModalController,
private publications:PublicationsService,
@@ -56,6 +59,48 @@ export class PublicationDetailPage implements OnInit {
};
}
@ViewChild('ScrollContainer', { static: true }) ScrollContainer: ElementRef;
private observer: IntersectionObserver;
ngAfterViewInit() {
// Create a callback function for the IntersectionObserver
const callback = (entries: IntersectionObserverEntry[]) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.onElementVisible();
}
});
};
// Create the IntersectionObserver instance
this.observer = new IntersectionObserver(callback, {
root: null, // Use the viewport as the root
threshold: 0.1 // Trigger the callback when 10% of the element is visible
});
// Start observing the target element
this.observer.observe(this.ScrollContainer.nativeElement);
}
onElementVisible() {
console.log('Element is visible!');
const e = this.ScrollContainer.nativeElement as HTMLDivElement
this.containerMaxHeight = e.clientHeight - 100;
this.containerMaxWidth = e.clientWidth - 40
window['e'] = e;
}
ngOnDestroy() {
// Unobserve the element when the component is destroyed
if (this.observer) {
this.observer.unobserve(this.ScrollContainer.nativeElement);
this.observer.disconnect();
}
}
ngOnInit() {
this.getPublicationDetail();
}
@@ -42,7 +42,7 @@
<ion-card *ngFor="let publication of publicationFolderService.publicationList[folderId] let i = index">
<ion-card-content>
<div style="width: 100%; height: 395px;">
<div style="width: 100%; height: 395px;overflow:hidden">
<app-swiper
[publicationList]=publication
[navigation]="true"
+4 -21
View File
@@ -4,14 +4,14 @@
</ion-toolbar>
</ion-header> -->
<ion-content>
<swiper-container #swipers [slidechange]="onSlideChange()" [navigation]="navigation" autoHeight="true">
<div>
<swiper-container id="C{{componentId}}" #swipers (click)="click()" [navigation]="navigation" autoHeight="true">
<swiper-slide *ngFor="let files of publicationList.Files let k = index">
<div >
<img *ngIf="checkFileType.checkFileType(files.FileExtension ) == 'image'" class="post-img"
[src]="'data:image/jpg;base64,' + files.FileBase64" loading="lazy">
<video #videoElement [appVisibility]="onVisibilityChange" *ngIf="checkFileType.checkFileType(files.FileExtension ) == 'video'" class="post-video" controls="controls" preload="none"
<video #videoElements [appVisibility]="onVisibilityChange" *ngIf="checkFileType.checkFileType(files.FileExtension ) == 'video'" class="post-video" controls="controls" preload="none"
playsinline webkit-playsinline="webkit-playsinline" (play)="stopvideoService.registerVideoWithEvent($event)"q>
<source [src]="files.FileBase64" type="video/mp4">
</video>
@@ -26,7 +26,7 @@
</swiper-container>
</ion-content>
</div>
<ion-footer>
<div *ngIf="pagination && publicationList?.Files?.length > 1" class="dots-container">
@@ -38,20 +38,3 @@
</span>
</div>
</ion-footer>
<!-- <ion-footer>
<div *ngIf="publicationList.Files.length > 1" class="dots-container">
<span *ngFor="let files of publicationList.Files; let k = index"
[class.dotsSwiper]="true"
[class.active-dot]="swiperIndex === k"
[class.small-dot-after]="k === swiperIndex + 5"
[class.small-dot-before]="k === swiperIndex - 5"
[class.smaller-dot-after]="k === swiperIndex + 6"
[class.smaller-dot-before]="k === swiperIndex - 6"
[class.visibility-hiden-dot-after]="k === swiperIndex + 7"
[class.visibility-hiden-dot-before]="k === swiperIndex - 7"
(click)="goToSlide(k)">
</span>
</div>
</ion-footer> -->
+48 -29
View File
@@ -1,9 +1,7 @@
import { Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core';
import { Component, ElementRef, Input, OnInit, ViewChild, OnChanges, ViewChildren, QueryList } from '@angular/core';
import { checkFileTypeService } from 'src/app/services/checkFileType.service';
import { StopvideoService } from 'src/app/services/stopvideo.service';
import { v4 as uuidv4 } from 'uuid'
@Component({
selector: 'app-swiper',
templateUrl: './swiper.page.html',
@@ -11,35 +9,45 @@ import { StopvideoService } from 'src/app/services/stopvideo.service';
})
export class SwiperPage implements OnInit {
@Input() publicationList: any;
@Input() navigation: boolean;
@Input() pagination: boolean;
swiperIndex: number = 0;
@ViewChild('VideoManager') VideoManager;
@ViewChild('videoElement') videoElements: ElementRef | undefined;
@ViewChildren('videoElement') videoElements: QueryList<ElementRef>;
@ViewChild('swipers')
swiperRef: ElementRef | undefined;
showControls: boolean = false;
previousIndex: number | undefined;
componentId = uuidv4()
constructor(
public checkFileType: checkFileTypeService,
public stopvideoService: StopvideoService,
) {
console.log('Income list',this.publicationList)
}
) {}
ngOnInit() {
setTimeout(() => {
var videos:NodeListOf<HTMLVideoElement> = document.querySelectorAll(`#C${this.componentId} video`);
try {
// Pause each video
videos.forEach(function (video) {
video.load();
})
} catch(e) {}
}, 100)
}
ngOnChanges() {
this.onSlideChange()
}
onSlideChange() {
console.log('onchage')
if (this.swiperRef) {
const swiper = this.swiperRef.nativeElement.swiper;
@@ -52,12 +60,13 @@ export class SwiperPage implements OnInit {
this.previousIndex = this.swiperIndex;
this.loadVideo()
} else {
console.log(this.swiperIndex)
}
console.log(this.swiperIndex)
}
}
goToSlide(index: number) {
this.swiperIndex = this.swiperRef?.nativeElement.swiper.activeIndex;
this.swiperRef?.nativeElement.swiper.slideTo(index);
@@ -79,35 +88,45 @@ export class SwiperPage implements OnInit {
video.pause();
})
const video: HTMLVideoElement = this.videoElements.nativeElement;
video.pause()
/* this.videoElements.forEach(videoElement => {
this.videoElements.forEach(videoElement => {
// You can access the native HTML video element using videoElement.nativeElement
const video: HTMLVideoElement = videoElement.nativeElement;
video.pause()
// Do something with each video element
// console.log(video);
}); */
});
} catch (error) {
console.log(error)
}
}
click() {
this.onSlideChange()
}
//This method does the refresh of all video(it was implement beacouse of the erro displaing the controls of the video while swipe)
//If you can find a better solution, please remove
loadVideo() {
var videos = document.querySelectorAll('video');
try {
// Pause each video
videos.forEach(function (video) {
video.load();
})
const video: HTMLVideoElement = this.videoElements.nativeElement;
const file = this.publicationList.Files[this.swiperIndex]
if(this.checkFileType.checkFileType(file.FileExtension ) == 'video') {
const video = document.querySelector(`#C${this.componentId} .swiper-slide-active video`) as HTMLVideoElement
if(video) {
// video.load();
video.removeAttribute('controls');
setTimeout(() => {
video.setAttribute('controls', 'controls');
}, 10)
}
}
video.load()
} catch (error) {
console.log(error)
}