This commit is contained in:
Peter Maquiran
2024-02-23 12:39:59 +01:00
29 changed files with 1742 additions and 877 deletions
@@ -53,45 +53,32 @@
X
</div>
<ion-img *ngIf="checkFileType.checkFileType(seleted.FileExtension) == 'image'"
name="image" ngDefaultControl [src]="seleted.FileBase64" style="height: 69px;"></ion-img>
<div *ngIf="checkFileType.checkFileType(seleted.FileExtension) == 'video' && seleted.chucksManager">
<mat-progress-bar
<!-- <div class="progress-container" *ngIf="checkFileType.checkFileType(seleted.FileExtension) == 'video'">
<div class="progress-bar" id="myProgressBar"></div>
</div> -->
<!-- <div *ngIf="checkFileType.checkFileType(seleted.FileExtension) == 'video'">
<mat-progress-bar
*ngIf="seleted.chucksManager"
mode="determinate"
style="width: 50%"
[style.width]="seleted.chucksManager.uploadPercentage"
></mat-progress-bar>
</div> -->
<video *ngIf="checkFileType.checkFileType(seleted.FileExtension) == 'video' && checkDesktop() == true" width="70" height="70"
</div>
<ion-img *ngIf="checkFileType.checkFileType(seleted.FileExtension) == 'image'"
name="image" ngDefaultControl [src]="seleted.url" style="height: 69px;"></ion-img>
<video class="sdf" *ngIf="checkFileType.checkFileType(seleted.FileExtension) == 'video' && checkDesktop() == true" width="70" height="70"
preload="metadata" webkit-playsinline="webkit-playsinline">
<source type="video/mp4" [src]="seleted.FileBase64">
<source type="video/mp4" [src]="seleted.url">
</video>
<video *ngIf="checkFileType.checkFileType(seleted.FileExtension) == 'video' && checkTableDivice() == true" width="70" height="70"
<video class="sdfsdf" *ngIf="checkFileType.checkFileType(seleted.FileExtension) == 'video' && checkTableDivice() == true" width="70" height="70"
preload="metadata" webkit-playsinline="webkit-playsinline">
<source type="video/mp4" [src]="seleted.FileBase64">
<source type="video/mp4" [src]="'data:video/mp4;base64,' + seleted.Base64">
</video>
</div>
</div>
<!-- Display the blurred image and count if there are more images -->
<div *ngIf="seletedContent.length > displayLimit" lot="start">
<ion-img [src]="'data:image/jpg;base64,' + seletedContent[displayLimit - 1].FileBase64"
style="filter: blur(5px);"></ion-img>
<p>mais {{ seletedContent.length - displayLimit }}</p>
</div>
<ion-label class="pl-10">
<p>{{capturedImageTitle}}</p>
@@ -121,7 +108,7 @@
</div>
</div>
</div>
<div *ngIf="photoOrVideo" class="container-multiselect pt-10" style="width: 200px;">
<button id="container-multiselect" class="multiselect-button" (click)="takePicture()">Fotografia</button>
<button id="container-multiselect" class="multiselect-button" (click)="startVideoRecording()">Video</button>
@@ -153,6 +140,10 @@
</div>
</ion-label>
</div>
<video id="yourVideoElementId" >videoss</video>
</div>
</ion-content>
File diff suppressed because one or more lines are too long
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { PublicationFormMVService } from './publication-form-mv.service';
describe('PublicationFormMVService', () => {
let service: PublicationFormMVService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(PublicationFormMVService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
@@ -0,0 +1,9 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class PublicationFormMVService {
constructor() { }
}
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { UploadStreamingService } from './upload-streaming.service';
describe('UploadStreamingService', () => {
let service: UploadStreamingService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(UploadStreamingService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
@@ -0,0 +1,512 @@
import { Injectable } from '@angular/core';
import { ok, err, Result } from 'neverthrow';
import { ObjectMergeNotification } from 'src/app/services/socket-connection-mcr.service';
import { CMAPIService } from "src/app/shared/repository/CMAPI/cmapi.service"
import { DomSanitizer } from '@angular/platform-browser';
export enum UploadError {
noConnection = 'noConnection',
slow = 'slow'
}
export type IOUploadError = "noConnection" | "slow"
@Injectable({
providedIn: 'root'
})
export class UploadStreamingService {
constructor(
private CMAPIService: CMAPIService,
private sanitizer: DomSanitizer
) {
window["sanitizer"] = this.sanitizer
}
}
class UploadFileUseCase {
CMAPIService: CMAPIService = window["CMAPIAPIRepository"]
constructor() {}
async execute(PublicationAttachmentEntity: PublicationAttachmentEntity): Promise<Result<true, IOUploadError >> {
return new Promise(async (resolve, reject) => {
let path: string;
const length = PublicationAttachmentEntity.chucksManager.chunks.totalChunks.toString()
const readAndUploadChunk = async(index: number) => {
const base64 = await PublicationAttachmentEntity.chucksManager.chunks.getChunks(index)
const uploadRequest = this.CMAPIService.FileContent({length, path: PublicationAttachmentEntity.chucksManager.path, index, base64})
uploadRequest.then((uploadRequest) => {
if(uploadRequest.isOk()) {
PublicationAttachmentEntity.chucksManager.setResponse(index, uploadRequest)
}
})
return uploadRequest;
}
if(!PublicationAttachmentEntity.chucksManager.hasPath()) {
const guidRequest = await this.CMAPIService.RequestUpload()
if(guidRequest.isOk()) {
path = guidRequest.value+".mp4"
PublicationAttachmentEntity.chucksManager.setPath(path)
} else {
const pingRequest = await this.CMAPIService.ping()
if( pingRequest.isErr()) {
return resolve(err(UploadError.noConnection))
} else {
return resolve(err(UploadError.slow))
}
}
}
const allRequest: Promise<any>[] = []
let connection = true
let errorMessage: UploadError.noConnection | UploadError.slow
for (let index = 1; ( (index <= PublicationAttachmentEntity.chucksManager.chunks.totalChunks) && connection ); index++) {
const needUpload = PublicationAttachmentEntity.chucksManager.needToUploadChunkIndex(index)
if(needUpload) {
// upload every chunk at onces
// const request = readAndUploadChunk(index).then(async(uploadRequest) => {
// if(uploadRequest.isErr()) {
// connection = false
// const pingRequest = await this.CMAPIService.ping()
// if( pingRequest.isErr()) {
// errorMessage = UploadError.noConnection
// } else {
// errorMessage = UploadError.slow
// }
// }
// })
// allRequest.push(request)
// one by one chunk upload
const request = readAndUploadChunk(index)
allRequest.push(request)
const uploadRequest = await request
if(uploadRequest.isErr()) {
const pingRequest = await this.CMAPIService.ping()
if( pingRequest.isErr()) {
return resolve(err(UploadError.noConnection))
} else {
return resolve(err(UploadError.slow))
}
}
}
}
await Promise.all(allRequest)
if(!connection) {
return resolve(err(errorMessage))
} else {
return resolve(ok(true))
}
})
}
}
export class PublicationAttachmentEntity {
url: string
FileExtension: string
FileType: 'image' | 'video'
OriginalFileName: string
blobFile?: File
toUpload = false;
chucksManager : ChucksManager
Base64: string
constructor({base64, extension, blobFile, OriginalFileName, FileType}:PublicationAttachmentEntityParams) {
this.Base64 = base64;
this.FileExtension = extension;
this.blobFile = blobFile
this.OriginalFileName = OriginalFileName
this.FileType = FileType
this.fixFileBase64();
}
fixFileBase64() {
const sanitizer : DomSanitizer = window["sanitizer"]
if(this.FileType == 'image' ) {
if(!this.Base64.startsWith('data:')) {
this.url = 'data:image/jpg;base64,' + this.Base64
// this.url = sanitizer.bypassSecurityTrustUrl('data:image/jpg;base64,' + this.Base64) as any
} else {
this.url = this.Base64
}
} else if (this.FileType == 'video' ) {
if(!this.Base64.startsWith('data:') && !this.Base64.startsWith('http')) {
this.url = 'data:video/mp4;base64,' + this.Base64
// this.url = sanitizer.bypassSecurityTrustUrl('data:video/mp4;base64,' + this.Base64) as any
} else {
this.url = this.Base64
}
}
}
needUpload() {
this.toUpload = true
}
setChunkManger (chunks: Chunks) {
this.chucksManager = new ChucksManager({chunks})
}
get hasChunkManger() {
return this.chucksManager?.chunks
}
get hasChunkManager() {
return this.chucksManager != null
}
}
interface IPublicationFormModelEntity {
DateIndex: any
DocumentId: any
ProcessId: any
Title: any
Message: any
DatePublication: any
Files: PublicationAttachmentEntity[]
}
interface PublicationAttachmentEntityParams {
base64: string,
blobFile?: File
extension: string,
OriginalFileName: string,
FileType: 'image' | 'video'
}
export class PublicationFormModel implements IPublicationFormModelEntity {
constructor() {}
DateIndex: any;
DocumentId: any;
ProcessId: any;
Title: any;
Message: any;
DatePublication: any;
OriginalFileName: string;
Files: PublicationAttachmentEntity[];
hasSet = false
setData(data: IPublicationFormModelEntity) {
if(!this.hasSet) {
Object.assign(this, data)
}
this.hasSet = true
}
}
export class PublicationFormMV {
private UploadFileUseCase = new UploadFileUseCase()
private form = new PublicationFormModel()
ObjectMergeNotification = new ObjectMergeNotification()
constructor() {
// this.ObjectMergeNotification.connect();
}
setDataToFrom(data: IPublicationFormModelEntity) {
this.form.setData(data)
}
private getVideoFiles() {
return this.form.Files.filter( x => x.FileType == 'video')
}
private upload(PublicationAttachmentEntity: PublicationAttachmentEntity) {
return new Promise(async (resolve, reject)=> {
if(!PublicationAttachmentEntity.hasChunkManger) {
const fileBlob = PublicationAttachmentEntity.blobFile;
const fileChunks = new Chunks({chunkSize: 40 })
fileChunks.setFile(fileBlob)
PublicationAttachmentEntity.setChunkManger(fileChunks)
} else if(PublicationAttachmentEntity.chucksManager.doneUpload) {
return resolve(true)
}
let attemp = 0;
let result: Result<true, IOUploadError>
if( PublicationAttachmentEntity.chucksManager.isUploading == false) {
do {
attemp++
PublicationAttachmentEntity.chucksManager.clearManualRetry()
PublicationAttachmentEntity.chucksManager.setUploading()
result = await this.UploadFileUseCase.execute(PublicationAttachmentEntity)
PublicationAttachmentEntity.chucksManager.clearUploading()
} while (attemp<3 && result.isErr() && result.error == 'slow')
if(result.isErr()) {
PublicationAttachmentEntity.chucksManager.setManualRetry()
resolve(false)
} else {
const guid = PublicationAttachmentEntity.chucksManager.path
this.ObjectMergeNotification.subscribe(guid, (data) => {
PublicationAttachmentEntity.chucksManager.contentSetReady()
resolve(true)
})
PublicationAttachmentEntity.chucksManager.doneChunkUpload()
this.ObjectMergeNotification.socket.commit(PublicationAttachmentEntity.chucksManager.path)
resolve(true)
}
} else {
}
})
}
uploadVideosFiles(): Promise<Boolean> {
return new Promise((resolve, reject) => {
// this.ObjectMergeNotification.socket.registerWhenConnected(() => {
const videosFiles = this.getVideoFiles()
const videosFilesToUploads = videosFiles.filter( e => e.FileType == "video")
const Promises: Promise<any>[] = []
for(const file of videosFilesToUploads) {
const promise = this.upload(file)
Promises.push(promise)
}
// Use Promise.all to wait for all promises to resolve
Promise.all(Promises)
.then((results) => {
// Check if every promise resolved successfully
const allPromisesResolvedSuccessfully = results.every((result) => result == true);
if (allPromisesResolvedSuccessfully) {
console.log('All promises resolved successfully.');
resolve(true)
} else {
resolve(false)
console.log('Some promises failed to resolve successfully.');
}
})
.catch((error) => {
resolve(false)
console.error('An error occurred while resolving promises:', error);
});
//})
})
}
}
export class Chunks {
chunkSize: number
private file: File
constructor({chunkSize}) {
this.chunkSize = chunkSize * 1024
}
get totalChunks () {
return Math.ceil(this.file.size / this.chunkSize);
}
setFile(file: File) {
this.file = file
}
// Function to read a chunk of the file
readChunk(start: number, end: number): any {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (event: any) => resolve(event.target.result.split(',')[1]);
reader.onerror = (error) => reject(error);
reader.readAsDataURL(this.file.slice(start, end));
});
}
async getChunks(i: number): Promise<string> {
i--
if(i < this.totalChunks) {
const start = i * this.chunkSize;
const end = Math.min(start + this.chunkSize, this.file.size);
const chunk = await this.readChunk(start, end);
return chunk
}
}
}
interface IUploadResponse {
result: Result<any, Error>
attemp: number
}
export class ChucksManager {
chunks: Chunks
uploads: {[key: string]: IUploadResponse } = {}
path: string = undefined
uploadPercentage: string = "1%"
merging = false
onSetPath: Function[] = []
onSetLastChunk: Function[] = []
contentReady = false
manualRetry = false
isUploading = false
subscribeToUseCaseResponse: Function[] = []
getUploadPercentage() {
return this.uploadPercentage
}
get uploadsCount() {
return Object.entries(this.uploads).length
}
get uploadWithSuccessCount() {
const uploadWithSuccess = Object.entries(this.uploads).filter(([index, data])=> data.result.isOk())
return uploadWithSuccess.length
}
get doneUpload() {
return this.chunks.totalChunks == this.uploadWithSuccessCount
}
uploadFunc: Function
constructor({chunks}) {
this.chunks = chunks
}
calculatePercentage(): number {
/**
* Calculate the percentage based on the total and current values.
*
* @param total - The total value.
* @param current - The current value.
* @returns The percentage calculated as (current / total) * 100.
*/
const total = this.chunks.totalChunks
const current = this.uploadWithSuccessCount
if (total === 0) {
return 0; // To avoid division by zero error
}
const percentage: number = (current / total) * 100;
return percentage;
}
setManualRetry() {
this.manualRetry = true
}
clearManualRetry() {
this.manualRetry = false
}
setUploading() {
this.isUploading = true
}
clearUploading() {
this.isUploading = false
}
setPercentage() {
const percentage: number = this.calculatePercentage()
console.log({percentage})
this.uploadPercentage = percentage.toString()+"%"
}
setPath(path: string) {
this.path = path
this.onSetPath.forEach(callback => callback());
}
registerOnSetPath(a: Function) {
this.onSetPath.push(a)
}
registerOnLastChunk(a: Function) {
this.onSetLastChunk.push(a)
}
registerToUseCaseResponse(a: Function) {
this.subscribeToUseCaseResponse.push(a)
}
hasPath() {
return this.path != undefined
}
isIndexRegistered(index) {
if(!this.uploads[index]) {
return false
}
return true
}
needToUploadChunkIndex(index) {
return !this.uploads?.[index]?.result?.isOk()
}
setResponse(index, UploadResponse) {
if(!this.isIndexRegistered(index)) {
this.uploads[index] = {
attemp: 1,
result: UploadResponse
}
console.log({UploadResponse})
} else {
this.uploads[index].attemp++;
this.uploads[index].result = UploadResponse
}
this.setPercentage()
}
doneChunkUpload() {
this.merging = true
this.onSetLastChunk.forEach(callback => callback());
}
contentSetReady() {
this.merging = false
this.contentReady = true
}
}
@@ -39,23 +39,25 @@
<p class="item-content-detail">{{publicationFolderService.FolderDetails[folderId].Detail}}</p>
<ion-card *ngFor="let publication of publicationFolderService.publicationList[folderId] let i = index">
<ion-card-content>
<swiper-container [modules]="swiperModules" [speed]=400 navigation="true" [pagination]="{clickable: true, dynamicBullets: true }">
<swiper-slide *ngFor="let files of publication.Files let k = index">
<div (click)="viewPublicationDetail(publication.DocumentId, publication.ProcessId)">
<img *ngIf="checkFileType.checkFileType(files.FileExtension ) == 'image'" class="post-img cursor-pointer"
[lazyLoad]="'data:image/jpg;base64,' + files.FileBase64">
<swiper-container [config]="swiperThumbsConfig" [modules]="swiperModules" [speed]=400 navigation="true" [pagination]="{clickable: true, dynamicBullets: true }">
<swiper-slide *ngFor="let files of publication.Files let k = index" class="centered-slide">
<div class="cool">
<div (click)="viewPublicationDetail(publication.DocumentId, publication.ProcessId)">
<img *ngIf="checkFileType.checkFileType(files.FileExtension ) == 'image'" class="post-img cursor-pointer"
[lazyLoad]="'data:image/jpg;base64,' + files.FileBase64">
<video [appVisibility]="onVisibilityChange" #myVideo #videoElement *ngIf="checkFileType.checkFileType(files.FileExtension ) == 'video'" class="post-video" controls="controls" preload="metadata"
webkit-playsinline="webkit-playsinline" class="videoPlayer" (click)="preventVideoPlay($event)">
<source [src]="files.FileBase64" type="video/mp4" >
</video>
<video [appVisibility]="onVisibilityChange" #myVideo #videoElement *ngIf="checkFileType.checkFileType(files.FileExtension ) == 'video'" class="post-video" controls="controls" preload="metadata"
webkit-playsinline="webkit-playsinline" class="videoPlayer" (click)="preventVideoPlay($event)">
<source [src]="files.FileBase64" type="video/mp4" >
</video>
</div>
</div>
</swiper-slide>
<!-- <div *ngIf="publication?.Files?.length == 0">
@@ -292,3 +292,10 @@ swiper-slide video {
align-items: center;
}
.centered-slide {
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
@@ -15,7 +15,7 @@ import { HttpErrorHandle } from 'src/app/services/http-error-handle.service';
import { PublicationFolderService } from 'src/app/store/publication-folder.service';
import { AskModalPage } from 'src/app/modals/ask-modal/ask-modal.page';
import { checkFileTypeService } from 'src/app/services/checkFileType.service';
import { PublicationVideoManagerService } from "src/app/services/publication/publication-video-manager.service";
import { PublicationVideoManagerService } from "src/app/services/publication/publication-video-manager.service";
import { StopvideoService } from "src/app/services/stopvideo.service";
@Component({
selector: 'app-view-publications',
@@ -29,6 +29,18 @@ export class ViewPublicationsPage implements OnInit {
error: any;
oldpublicationIds = []
swiperConfig = {
spaceBetween: 10,
navigation: true,
}
swiperThumbsConfig = {
spaceBetween: 10,
slidesPerView: 4,
freeMode: true,
watchSlidesProgress: true,
}
@Input() folderId: any;
@Output() addNewPublication = new EventEmitter<any>();
@Output() editPublication = new EventEmitter<any>();
@@ -80,7 +92,7 @@ export class ViewPublicationsPage implements OnInit {
onVisibilityChange = (e: boolean) => {
console.log("nice to have", e)
if (!e) {
if(!e) {
/* this.stopVideo() */
}
}
@@ -228,13 +240,13 @@ export class ViewPublicationsPage implements OnInit {
this.publicationFolderService.revertPublicationOrder(folderId)
} else {
let a: any = Object.assign({}, this.publicationFolderService.publicationList[folderId][findIndex])
let a: any = Object.assign({},this.publicationFolderService.publicationList[folderId][findIndex])
let b: any = Object.assign({}, publicationDetails)
a.Files = a.Files.length
b.Files = b.Files.length
if (JSON.stringify(a) != JSON.stringify(b)) {
if(JSON.stringify(a) != JSON.stringify(b)) {
// console.log({a, b})
this.publicationFolderService.publicationList[folderId][findIndex] = publicationDetails
@@ -314,12 +326,11 @@ export class ViewPublicationsPage implements OnInit {
},
});
modal.onDidDismiss().then(async (res) => {
modal.onDidDismiss().then((res) => {
if (res.data == 'Yes') {
const loader = this.toastService.loading();
try {
await this.publications.DeletePresidentialAction(folderId).toPromise();
window["refreshPublication"]();
this.publications.DeletePresidentialAction(folderId).toPromise();
this.httpErrorHandle.httpsSucessMessagge('Eliminar Acção')
} catch (error) {
this.httpErrorHandle.httpStatusHandle(error)
@@ -329,8 +340,6 @@ export class ViewPublicationsPage implements OnInit {
}
this.close();
this.getActions.emit();
}
// Do nothing
});
@@ -343,7 +352,7 @@ export class ViewPublicationsPage implements OnInit {
this.stopVideo();
setTimeout(() => {
setTimeout(()=> {
this.openPublicationDetails.emit({ DocumentId, ProcessId });
}, 200)
@@ -370,12 +379,12 @@ export class ViewPublicationsPage implements OnInit {
stopVideo() {
var videos = document.querySelectorAll('video');
try {
// Pause each video
videos.forEach(function (video) {
video.pause();
})
// Pause each video
videos.forEach(function (video) {
video.pause();
})
try {
this.videoElements.forEach(videoElement => {
// You can access the native HTML video element using videoElement.nativeElement
@@ -385,20 +394,20 @@ export class ViewPublicationsPage implements OnInit {
// Do something with each video element
// console.log(video);
});
} catch (e) {
} catch (error) {
console.log(error)
}
}
public onScroll(event): void {
if (this.lastScrollTop < event.detail.scrollTop) {
if(this.lastScrollTop < event.detail.scrollTop) {
// console.log("scrolling down")
} else {
} else {
// console.log("scrolling up")
}
}
this.lastScrollTop = event.detail.scrollTop;
this.lastScrollTop = event.detail.scrollTop;
}
}