make video conference work
This commit is contained in:
		
							parent
							
								
									9ea1a6f3d6
								
							
						
					
					
						commit
						1e89e52b12
					
				
							
								
								
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -360,4 +360,7 @@ MigrationBackup/ | ||||
| 
 | ||||
| # Fody - auto-generated XML schema | ||||
| FodyWeavers.xsd | ||||
| /EnotaryoPH/EnotaryoPH.Web/wwwroot/dist/ | ||||
| 
 | ||||
| 
 | ||||
| .parcel*/ | ||||
| /EnotaryoPH/EnotaryoPH.Web/wwwroot/dist | ||||
|  | ||||
| @ -0,0 +1,11 @@ | ||||
| namespace EnotaryoPH.Data.Constants | ||||
| { | ||||
|     public enum VideoConferenceStatus | ||||
|     { | ||||
|         New = 0, | ||||
|         InProgress = 1, | ||||
|         Expired = 49, | ||||
|         Abandoned = 50, | ||||
|         Completed = 100 | ||||
|     } | ||||
| } | ||||
| @ -5,23 +5,37 @@ namespace EnotaryoPH.Data.Entities | ||||
|     [Table("Lawyers")] | ||||
|     public class Lawyer | ||||
|     { | ||||
|         [Column("LawyerID")] | ||||
|         public int LawyerID { get; set; } | ||||
|         [Column("CommissionExpiration")] | ||||
|         public DateTime? CommissionExpiration { get; set; } | ||||
| 
 | ||||
|         [Column("Rollnumber")] | ||||
|         public string? Rollnumber { get; set; } | ||||
|         [Column("CommissionLocation")] | ||||
|         public string? CommissionLocation { get; set; } | ||||
| 
 | ||||
|         [Column("CommissionNumber")] | ||||
|         public string? CommissionNumber { get; set; } | ||||
| 
 | ||||
|         [Column("CreatedOn")] | ||||
|         public DateTime? CreatedOn { get; set; } | ||||
| 
 | ||||
|         [Column("IBPNumber")] | ||||
|         public string? IBPNumber { get; set; } | ||||
| 
 | ||||
|         [Column("Lawyer_UID")] | ||||
|         public Guid? Lawyer_UID { get; set; } | ||||
| 
 | ||||
|         [Column("LawyerID")] | ||||
|         public int LawyerID { get; set; } | ||||
| 
 | ||||
|         public List<LawyerVideoConferenceSchedule> LawyerVideoConferenceSchedules { get; set; } | ||||
| 
 | ||||
|         [Column("MCLEComplianceNumber")] | ||||
|         public string? MCLEComplianceNumber { get; set; } | ||||
| 
 | ||||
|         [Column("MCLEDate")] | ||||
|         public DateTime? MCLEDate { get; set; } | ||||
| 
 | ||||
|         [Column("PTRNumber")] | ||||
|         public string? PTRNumber { get; set; } | ||||
|         [Column("OfficeAddress")] | ||||
|         public string? OfficeAddress { get; set; } | ||||
| 
 | ||||
|         [Column("PTRDate")] | ||||
|         public DateTime? PTRDate { get; set; } | ||||
| @ -29,31 +43,19 @@ namespace EnotaryoPH.Data.Entities | ||||
|         [Column("PTRlocation")] | ||||
|         public string? PTRlocation { get; set; } | ||||
| 
 | ||||
|         [Column("OfficeAddress")] | ||||
|         public string? OfficeAddress { get; set; } | ||||
|         [Column("PTRNumber")] | ||||
|         public string? PTRNumber { get; set; } | ||||
| 
 | ||||
|         [Column("UserID")] | ||||
|         public int UserID { get; set; } | ||||
| 
 | ||||
|         [Column("Lawyer_UID")] | ||||
|         public Guid? Lawyer_UID { get; set; } | ||||
| 
 | ||||
|         [Column("CreatedOn")] | ||||
|         public DateTime? CreatedOn { get; set; } | ||||
| 
 | ||||
|         [Column("CommissionNumber")] | ||||
|         public string? CommissionNumber { get; set; } | ||||
| 
 | ||||
|         [Column("CommissionLocation")] | ||||
|         public string? CommissionLocation { get; set; } | ||||
| 
 | ||||
|         [Column("CommissionExpiration")] | ||||
|         public DateTime? CommissionExpiration { get; set; } | ||||
|         [Column("Rollnumber")] | ||||
|         public string? Rollnumber { get; set; } | ||||
| 
 | ||||
|         [Column("Status")] | ||||
|         public string? Status { get; set; } | ||||
| 
 | ||||
|         [ForeignKey("UserID")] | ||||
|         public User User { get; set; } | ||||
| 
 | ||||
|         [Column("UserID")] | ||||
|         public int UserID { get; set; } | ||||
|     } | ||||
| } | ||||
| @ -5,28 +5,34 @@ namespace EnotaryoPH.Data.Entities | ||||
|     [Table("LawyerVideoConferenceParticipants")] | ||||
|     public class LawyerVideoConferenceParticipant | ||||
|     { | ||||
|         [Column("LawyerVideoConferenceParticipantID")] | ||||
|         public int LawyerVideoConferenceParticipantID { get; set; } | ||||
| 
 | ||||
|         [Column("LawyerVideoConferenceScheduleID")] | ||||
|         public int LawyerVideoConferenceScheduleID { get; set; } | ||||
| 
 | ||||
|         [Column("ParticipantID")] | ||||
|         public int? ParticipantID { get; set; } | ||||
| 
 | ||||
|         [Column("Status")] | ||||
|         public string? Status { get; set; } | ||||
| 
 | ||||
|         [Column("CreatedOn")] | ||||
|         public DateTime? CreatedOn { get; set; } | ||||
| 
 | ||||
|         [Column("LawyerVideoConferenceParticipant_UID")] | ||||
|         public Guid? LawyerVideoConferenceParticipant_UID { get; set; } | ||||
| 
 | ||||
|         [ForeignKey("ParticipantID")] | ||||
|         public User Participant { get; set; } | ||||
|         [Column("LawyerVideoConferenceParticipantID")] | ||||
|         public int LawyerVideoConferenceParticipantID { get; set; } | ||||
| 
 | ||||
|         [ForeignKey("LawyerVideoConferenceScheduleID")] | ||||
|         public LawyerVideoConferenceSchedule LawyerVideoConferenceSchedule { get; set; } | ||||
| 
 | ||||
|         [Column("LawyerVideoConferenceScheduleID")] | ||||
|         public int LawyerVideoConferenceScheduleID { get; set; } | ||||
| 
 | ||||
|         [Column("MeetingRoomTokenID")] | ||||
|         public string? MeetingRoomTokenID { get; set; } | ||||
| 
 | ||||
|         [Column("MeetingRoomUserID")] | ||||
|         public string? MeetingRoomUserID { get; set; } | ||||
| 
 | ||||
|         [ForeignKey("ParticipantID")] | ||||
|         public User Participant { get; set; } | ||||
| 
 | ||||
|         [Column("ParticipantID")] | ||||
|         public int? ParticipantID { get; set; } | ||||
| 
 | ||||
|         [Column("Status")] | ||||
|         public string? Status { get; set; } | ||||
|     } | ||||
| } | ||||
| @ -5,27 +5,39 @@ namespace EnotaryoPH.Data.Entities | ||||
|     [Table("LawyerVideoConferenceSchedule")] | ||||
|     public class LawyerVideoConferenceSchedule | ||||
|     { | ||||
|         [Column("LawyerVideoConferenceScheduleID")] | ||||
|         public int LawyerVideoConferenceScheduleID { get; set; } | ||||
|         [Column("CreatedOn")] | ||||
|         public DateTime? CreatedOn { get; set; } | ||||
| 
 | ||||
|         [ForeignKey("LawyerID")] | ||||
|         public Lawyer Lawyer { get; set; } | ||||
| 
 | ||||
|         [Column("LawyerID")] | ||||
|         public int LawyerID { get; set; } | ||||
| 
 | ||||
|         [Column("TransactionID")] | ||||
|         public int TransactionID { get; set; } | ||||
| 
 | ||||
|         [Column("MeetingDate")] | ||||
|         public DateTime MeetingDate { get; set; } | ||||
| 
 | ||||
|         [Column("CreatedOn")] | ||||
|         public DateTime? CreatedOn { get; set; } | ||||
|         public List<LawyerVideoConferenceParticipant> LawyerVideoConferenceParticipants { get; set; } | ||||
| 
 | ||||
|         [Column("LawyerVideoConferenceSchedule_UID")] | ||||
|         public Guid LawyerVideoConferenceSchedule_UID { get; set; } | ||||
| 
 | ||||
|         [Column("LawyerVideoConferenceScheduleID")] | ||||
|         public int LawyerVideoConferenceScheduleID { get; set; } | ||||
| 
 | ||||
|         [Column("MeetingDate")] | ||||
|         public DateTime MeetingDate { get; set; } | ||||
| 
 | ||||
|         [Column("MeetingRoomID")] | ||||
|         public string? MeetingRoomID { get; set; } | ||||
| 
 | ||||
|         [Column("MeetingRoomTokenID")] | ||||
|         public string? MeetingRoomTokenID { get; set; } | ||||
| 
 | ||||
|         [Column("MeetingRoomUserID")] | ||||
|         public string? MeetingRoomUserID { get; set; } | ||||
| 
 | ||||
|         [Column("Status")] | ||||
|         public string? Status { get; set; } | ||||
| 
 | ||||
|         public List<LawyerVideoConferenceParticipant> LawyerVideoConferenceParticipants { get; set; } | ||||
|         [Column("TransactionID")] | ||||
|         public int TransactionID { get; set; } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,213 @@ | ||||
| import { AzureCommunicationTokenCredential } from '@azure/communication-common'; | ||||
| import { CallClient, LocalVideoStream, VideoStreamRenderer } from '@azure/communication-calling'; | ||||
| 
 | ||||
| class VideoCall { | ||||
|   constructor() { | ||||
|     this.callClient = null; | ||||
|     this.callAgent = null; | ||||
|     this.deviceManager = null; | ||||
|     this.localVideoStream = null; | ||||
|     this.call = null; | ||||
|     this.videoElement = document.createElement('video'); | ||||
|     this.videoElement.setAttribute('autoplay', ''); | ||||
|     this.videoElement.setAttribute('muted', ''); | ||||
|     this.stateChangedCallback = null; | ||||
|     this.remoteParticipantsUpdated = null; | ||||
|     this.isLocalVideoStartedChanged = null; | ||||
|     this.localVideoStreamsUpdated = null; | ||||
|     this.idChanged = null; | ||||
|     this.onCreateLocalVideoStream = null; | ||||
|     this.remoteParticipantStateChanged = null; | ||||
|     this.remoteVideoIsAvailableChanged = null; | ||||
|   } | ||||
| 
 | ||||
|   async init(userAccessToken, options) { | ||||
|     this.stateChangedCallback = options.stateChangedCallback; | ||||
|     this.remoteParticipantsUpdated = options.remoteParticipantsUpdated; | ||||
|     this.isLocalVideoStartedChanged = options.isLocalVideoStartedChanged; | ||||
|     this.localVideoStreamsUpdated = options.localVideoStreamsUpdated; | ||||
|     this.idChanged = options.idChanged; | ||||
|     this.onCreateLocalVideoStream = options.onCreateLocalVideoStream; | ||||
|     this.remoteParticipantStateChanged = options.remoteParticipantStateChanged; | ||||
|     this.remoteVideoIsAvailableChanged = options.remoteVideoIsAvailableChanged; | ||||
| 
 | ||||
|     const tokenCredential = new AzureCommunicationTokenCredential(userAccessToken); | ||||
| 
 | ||||
|     this.callClient = new CallClient(); | ||||
|     this.callAgent = await this.callClient.createCallAgent(tokenCredential); | ||||
| 
 | ||||
|     this.deviceManager = await this.callClient.getDeviceManager(); | ||||
|     await this.deviceManager.askDevicePermission({ audio: true, video: true }); | ||||
|     const cameras = await this.deviceManager.getCameras(); | ||||
|     this.localVideoStream = new LocalVideoStream(cameras[0]); | ||||
|   } | ||||
| 
 | ||||
|   async startLocalVideo() { | ||||
|     if (this.localVideoStream) { | ||||
|       //const localVideoOptions = new VideoStreamOptions();
 | ||||
|       await this.callAgent.startCall([this.callAgent.identity], { video: [this.localVideoStream] }); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   stopLocalVideo() { | ||||
|     if (this.call) { | ||||
|       this.call.stopVideo(this.localVideoStream); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   async joinRoom(roomId) { | ||||
|     // Assuming you have a room ID and call the appropriate ACS API to join a room
 | ||||
|     // This is just a placeholder, replace with actual logic
 | ||||
|     console.log('Joining room:', roomId); | ||||
|     if (this.callAgent) { | ||||
|       try { | ||||
|         const localVideoStream = await this.createLocalVideoStream(); | ||||
|         const videoOptions = localVideoStream ? { localVideoStreams: [localVideoStream] } : undefined; | ||||
| 
 | ||||
|         const roomCallLocator = { roomId: roomId }; | ||||
|         let call = this.callAgent.join(roomCallLocator, { videoOptions }); | ||||
|         this.subscribeToCall(call); | ||||
|       } catch (error) { | ||||
|         console.error(error); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   async subscribeToCall(call) { | ||||
|     try { | ||||
|       call.on('idChanged', () => { | ||||
|         this.idChanged?.(call.id); | ||||
|       }); | ||||
|       call.on('stateChanged', async () => { | ||||
|         this.stateChangedCallback?.(call.state); | ||||
|       }); | ||||
|       call.on('isLocalVideoStartedChanged', () => { | ||||
|         this.isLocalVideoStartedChanged?.(call.isLocalVideoStarted); | ||||
|       }); | ||||
|       call.on('localVideoStreamsUpdated', e => { | ||||
|         this.localVideoStreamsUpdated?.(e); | ||||
|       }); | ||||
| 
 | ||||
|       // Subscribe to the call's 'remoteParticipantsUpdated' event to be
 | ||||
|       // notified when new participants are added to the call or removed from the call.
 | ||||
|       call.on('remoteParticipantsUpdated', e => { | ||||
|         this.remoteParticipantsUpdated?.(e); | ||||
| 
 | ||||
|         e.added.forEach(remoteParticipant => { | ||||
|           this.subscribeToRemoteParticipant(remoteParticipant) | ||||
|         }); | ||||
|         // Unsubscribe from participants that are removed from the call
 | ||||
|         e.removed.forEach(remoteParticipant => { | ||||
|           console.log('Remote participant removed from the call.'); | ||||
|         }); | ||||
|       }); | ||||
| 
 | ||||
|       call.localVideoStreams.forEach(async (lvs) => { | ||||
|         this.localVideoStream = lvs; | ||||
|         await this.displayLocalVideoStream(lvs); | ||||
|       }); | ||||
| 
 | ||||
|       // Inspect the call's current remote participants and subscribe to them.
 | ||||
|       call.remoteParticipants.forEach(remoteParticipant => { | ||||
|         this.subscribeToRemoteParticipant(remoteParticipant); | ||||
|       }); | ||||
|     } catch (error) { | ||||
|       console.error(error); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   subscribeToRemoteParticipant(remoteParticipant) { | ||||
|     try { | ||||
|       // Inspect the initial remoteParticipant.state value.
 | ||||
|       console.log(`Remote participant state: ${remoteParticipant.state}`); | ||||
|       // Subscribe to remoteParticipant's 'stateChanged' event for value changes.
 | ||||
|       remoteParticipant.on('stateChanged', () => { | ||||
|         console.log(`Remote participant state changed: ${remoteParticipant.state}`, JSON.stringify(remoteParticipant)); | ||||
|         this.remoteParticipantStateChanged?.(remoteParticipant.state); | ||||
|       }); | ||||
| 
 | ||||
|       // Inspect the remoteParticipants's current videoStreams and subscribe to them.
 | ||||
|       remoteParticipant.videoStreams.forEach(remoteVideoStream => { | ||||
|         this.subscribeToRemoteVideoStream(remoteVideoStream); | ||||
|       }); | ||||
|       // Subscribe to the remoteParticipant's 'videoStreamsUpdated' event to be
 | ||||
|       // notified when the remoteParticipant adds new videoStreams and removes video streams.
 | ||||
|       remoteParticipant.on('videoStreamsUpdated', e => { | ||||
|         // Subscribe to new remote participant's video streams that were added.
 | ||||
|         e.added.forEach(remoteVideoStream => { | ||||
|           this.subscribeToRemoteVideoStream(remoteVideoStream); | ||||
|         }); | ||||
|         // Unsubscribe from remote participant's video streams that were removed.
 | ||||
|         e.removed.forEach(remoteVideoStream => { | ||||
|           console.log('Remote participant video stream was removed.'); | ||||
|         }) | ||||
|       }); | ||||
|     } catch (error) { | ||||
|       console.error(error); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   async subscribeToRemoteVideoStream(remoteVideoStream) { | ||||
|     let renderer = new VideoStreamRenderer(remoteVideoStream); | ||||
|     let view; | ||||
| 
 | ||||
|     const createView = async () => { | ||||
|       // Create a renderer view for the remote video stream.
 | ||||
|       view = await renderer.createView(); | ||||
|       this.remoteVideoIsAvailableChanged?.({ | ||||
|         isAvailable: remoteVideoStream.isAvailable, | ||||
|         participantId: remoteVideoStream.tsParticipantId, | ||||
|         el: view.target | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
|     // Remote participant has switched video on/off
 | ||||
|     remoteVideoStream.on('isAvailableChanged', async () => { | ||||
|       try { | ||||
|         if (remoteVideoStream.isAvailable) { | ||||
|           await createView(); | ||||
|         } else { | ||||
|           view?.dispose(); | ||||
|         } | ||||
|       } catch (e) { | ||||
|         console.error(e); | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     // Remote participant has video on initially.
 | ||||
|     if (remoteVideoStream.isAvailable) { | ||||
|       try { | ||||
|         await createView(); | ||||
|       } catch (e) { | ||||
|         console.error(e); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   async createLocalVideoStream() { | ||||
|     const camera = (await this.deviceManager.getCameras())[0]; | ||||
|     if (camera) { | ||||
|       return new LocalVideoStream(camera); | ||||
|     } else { | ||||
|       console.error(`No camera device found on the system`); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   leaveRoom() { | ||||
|     if (this.call) { | ||||
|       this.call.hangUp(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   async displayLocalVideoStream(lvs) { | ||||
|     try { | ||||
|       let localVideoStreamRenderer = new VideoStreamRenderer(lvs); | ||||
|       const view = await localVideoStreamRenderer.createView(); | ||||
|       this.onCreateLocalVideoStream?.(view.target); | ||||
|     } catch (error) { | ||||
|       console.error(error); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export default VideoCall; | ||||
| @ -1,40 +1,38 @@ | ||||
| jfa.components.dialog = (function () { | ||||
|   function _confirm({ message, title, callbackyes, callbackno, yesLabel, noLabel }) { | ||||
|     const dialogTemplate = jfa.page.getDialogTemplate().content; | ||||
| function _confirm({ message, title, callbackyes, callbackno, yesLabel, noLabel }) { | ||||
|   const dialogTemplate = jfa.page.getDialogTemplate().content; | ||||
| 
 | ||||
|     let spanTitle = dialogTemplate.querySelector(".modal__title__text"); | ||||
|     spanTitle.textContent = title || "Confirm"; | ||||
|   let spanTitle = dialogTemplate.querySelector(".modal__title__text"); | ||||
|   spanTitle.textContent = title || "Confirm"; | ||||
| 
 | ||||
|     let bodyMessage = dialogTemplate.querySelector(".modal__body-message"); | ||||
|     bodyMessage.textContent = message; | ||||
|   let bodyMessage = dialogTemplate.querySelector(".modal__body-message"); | ||||
|   bodyMessage.textContent = message; | ||||
| 
 | ||||
|     const dialog = dialogTemplate.querySelector("#dialog-container"); | ||||
|     const modal = bootstrap.Modal.getOrCreateInstance(dialog); | ||||
|   const dialog = dialogTemplate.querySelector("#dialog-container"); | ||||
|   const modal = bootstrap.Modal.getOrCreateInstance(dialog); | ||||
| 
 | ||||
|     let buttonNo = dialogTemplate.querySelector(".modal__footer__button-no"); | ||||
|     if (noLabel) { | ||||
|       buttonNo.textContent = noLabel; | ||||
|     } | ||||
|     buttonNo.addEventListener("click", function () { | ||||
|       buttonNo.blur(); | ||||
|       modal.hide(); | ||||
|       callbackno?.(); | ||||
|     }); | ||||
| 
 | ||||
|     let buttonYes = dialogTemplate.querySelector(".modal__footer__button-yes"); | ||||
|     if (yesLabel) { | ||||
|       buttonYes.textContent = yesLabel; | ||||
|     } | ||||
|     buttonYes.addEventListener("click", function () { | ||||
|       buttonYes.blur(); | ||||
|       modal.hide(); | ||||
|       callbackyes?.(); | ||||
|     }); | ||||
| 
 | ||||
|     modal.show(); | ||||
|   let buttonNo = dialogTemplate.querySelector(".modal__footer__button-no"); | ||||
|   if (noLabel) { | ||||
|     buttonNo.textContent = noLabel; | ||||
|   } | ||||
|   buttonNo.addEventListener("click", function () { | ||||
|     buttonNo.blur(); | ||||
|     modal.hide(); | ||||
|     callbackno?.(); | ||||
|   }); | ||||
| 
 | ||||
|   return { | ||||
|     confirm: _confirm | ||||
|   }; | ||||
| })(); | ||||
|   let buttonYes = dialogTemplate.querySelector(".modal__footer__button-yes"); | ||||
|   if (yesLabel) { | ||||
|     buttonYes.textContent = yesLabel; | ||||
|   } | ||||
|   buttonYes.addEventListener("click", function () { | ||||
|     buttonYes.blur(); | ||||
|     modal.hide(); | ||||
|     callbackyes?.(); | ||||
|   }); | ||||
| 
 | ||||
|   modal.show(); | ||||
| } | ||||
| 
 | ||||
| export default { | ||||
|   confirm: _confirm | ||||
| }; | ||||
|  | ||||
| @ -1,5 +1,4 @@ | ||||
| jfa.page = (function () { | ||||
|   function _getAlertContainer() { | ||||
|   function _getAlertContainer() { | ||||
|     return document.getElementById("ContainerAlert"); | ||||
|   } | ||||
| 
 | ||||
| @ -21,14 +20,15 @@ | ||||
| 
 | ||||
|   function reload() { | ||||
|     window.location.reload(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|   return { | ||||
|     getAlertContainer: _getAlertContainer, | ||||
|     getAlertTemplate: _getAlertTemplate, | ||||
|     getAntiForgeryToken: _getAntiForgeryToken, | ||||
|     getDialogContainer: _getDialogContainer, | ||||
|     getDialogTemplate: _getDialogTemplate, | ||||
|     reload: reload | ||||
|   }; | ||||
| })(); | ||||
| const page = { | ||||
|   getAlertContainer: _getAlertContainer, | ||||
|   getAlertTemplate: _getAlertTemplate, | ||||
|   getAntiForgeryToken: _getAntiForgeryToken, | ||||
|   getDialogContainer: _getDialogContainer, | ||||
|   getDialogTemplate: _getDialogTemplate, | ||||
|   reload: reload | ||||
| } | ||||
| 
 | ||||
| export default { page }; | ||||
| @ -1,37 +1,36 @@ | ||||
| jfa.utilities.element = (function () { | ||||
|   function disable(element) { | ||||
|     if (!element) { | ||||
|       return; | ||||
|     } | ||||
|     element.disabled = true; | ||||
|  | ||||
| function disable(element) { | ||||
|   if (!element) { | ||||
|     return; | ||||
|   } | ||||
|   element.disabled = true; | ||||
| } | ||||
| 
 | ||||
| function enable(element) { | ||||
|   if (!element) { | ||||
|     return; | ||||
|   } | ||||
|   element.disabled = false; | ||||
| } | ||||
| 
 | ||||
| function hide(element) { | ||||
|   if (!element) { | ||||
|     return; | ||||
|   } | ||||
|   element.hidden = true; | ||||
| } | ||||
| 
 | ||||
| function show(element, suppressEvent) { | ||||
|   if (!element) { | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   function enable(element) { | ||||
|     if (!element) { | ||||
|       return; | ||||
|     } | ||||
|     element.disabled = false; | ||||
|   } | ||||
|   element.hidden = false; | ||||
| } | ||||
| 
 | ||||
|   function hide(element) { | ||||
|     if (!element) { | ||||
|       return; | ||||
|     } | ||||
|     element.hidden = true; | ||||
|   } | ||||
| 
 | ||||
|   function show(element, suppressEvent) { | ||||
|     if (!element) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     element.hidden = false; | ||||
|   } | ||||
| 
 | ||||
|   return { | ||||
|     disable: disable, | ||||
|     enable: enable, | ||||
|     hide: hide, | ||||
|     show: show | ||||
|   }; | ||||
| })(); | ||||
| export default { | ||||
|   disable: disable, | ||||
|   enable: enable, | ||||
|   hide: hide, | ||||
|   show: show | ||||
| }; | ||||
|  | ||||
| @ -1,28 +1,26 @@ | ||||
| jfa.utilities.request = (function () { | ||||
|  | ||||
| async function del(url, data) { | ||||
|   return await _fetch(url, data, "DELETE"); | ||||
| } | ||||
| 
 | ||||
|   async function del(url, data) { | ||||
|     return await _fetch(url, data, "DELETE"); | ||||
|   } | ||||
| async function _fetch(url, data, method) { | ||||
|   const response = await fetch(url, { | ||||
|     method: method, | ||||
|     headers: { | ||||
|       'Accept': 'application/json', | ||||
|       'Content-Type': 'application/json', | ||||
|       'RequestVerificationToken': jfa.page.getAntiForgeryToken() | ||||
|     }, | ||||
|     body: data ? JSON.stringify(data) : data | ||||
|   }); | ||||
|   return response; | ||||
| } | ||||
| 
 | ||||
|   async function _fetch(url, data, method) { | ||||
|     const response = await fetch(url, { | ||||
|       method: method, | ||||
|       headers: { | ||||
|         'Accept': 'application/json', | ||||
|         'Content-Type': 'application/json', | ||||
|         'RequestVerificationToken': jfa.page.getAntiForgeryToken() | ||||
|       }, | ||||
|       body: data ? JSON.stringify(data) : data | ||||
|     }); | ||||
|     return response; | ||||
|   } | ||||
| async function post(url, data) { | ||||
|   return await _fetch(url, data, "POST"); | ||||
| } | ||||
| 
 | ||||
|   async function post(url, data) { | ||||
|     return await _fetch(url, data, "POST"); | ||||
|   } | ||||
| 
 | ||||
|   return { | ||||
|     delete: del, | ||||
|     post: post | ||||
|   }; | ||||
| })(); | ||||
| export default { | ||||
|   delete: del, | ||||
|   post: post | ||||
| }; | ||||
|  | ||||
| @ -1,16 +1,16 @@ | ||||
| jfa.utilities.routing = (function () { | ||||
|   function getCurrentURL() { | ||||
|     return new URL(window.location.origin + window.location.pathname); | ||||
|   } | ||||
| function getCurrentURL() { | ||||
|   return new URL(window.location.origin + window.location.pathname); | ||||
| } | ||||
| 
 | ||||
| function getCurrentURLWithHandler(handler) { | ||||
|   let url = getCurrentURL(); | ||||
|   url.searchParams.append("handler", handler); | ||||
|   return url; | ||||
| } | ||||
| 
 | ||||
| export default { | ||||
|   getCurrentURL: getCurrentURL, | ||||
|   getCurrentURLWithHandler: getCurrentURLWithHandler | ||||
| } | ||||
| 
 | ||||
|   function getCurrentURLWithHandler(handler) { | ||||
|     let url = getCurrentURL(); | ||||
|     url.searchParams.append("handler", handler); | ||||
|     return url; | ||||
|   } | ||||
| 
 | ||||
|   return { | ||||
|     getCurrentURL: getCurrentURL, | ||||
|     getCurrentURLWithHandler: getCurrentURLWithHandler | ||||
|   }; | ||||
| })(); | ||||
| @ -1,5 +1,25 @@ | ||||
| "use strict"; | ||||
| var jfa = { | ||||
|   components: {}, | ||||
|   utilities: {} | ||||
| }; | ||||
| 
 | ||||
| import page from "../../Assets/js/Page/_Page.js"; | ||||
| import routing from "../../Assets/js/Utilities/Routing/_Routing.js"; | ||||
| import element from "../../Assets/js/Utilities/Element/_Element.js"; | ||||
| import request from "../../Assets/js/Utilities/Request/_Request.js"; | ||||
| import dialog from "../../Assets/js/Components/Dialog/_Dialog.js"; | ||||
| import videocall from "../../Assets/js/Communication/VideoCall/_VideoCall.js"; | ||||
| 
 | ||||
| const jfa = { | ||||
|   components: { | ||||
|     dialog | ||||
|   }, | ||||
|   utilities: { | ||||
|     routing, | ||||
|     element, | ||||
|     request | ||||
|   }, | ||||
|   page, | ||||
|   communication: { | ||||
|     videocall: new videocall() | ||||
|   } | ||||
| }; | ||||
| export default { jfa }; | ||||
| window.jfa = jfa; | ||||
| @ -8,6 +8,8 @@ | ||||
| 	</PropertyGroup> | ||||
| 
 | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="Azure.Communication.Identity" Version="1.3.1" /> | ||||
| 		<PackageReference Include="Azure.Communication.Rooms" Version="1.1.1" /> | ||||
| 		<PackageReference Include="CompreFace.NET.Sdk" Version="1.0.2" /> | ||||
| 		<PackageReference Include="Coravel" Version="6.0.2" /> | ||||
| 		<PackageReference Include="Coravel.Mailer" Version="7.1.0" /> | ||||
| @ -30,5 +32,7 @@ | ||||
| 	  <Folder Include="wwwroot\lib\signalr\" /> | ||||
| 	</ItemGroup> | ||||
| 
 | ||||
| 
 | ||||
| 	<Target Name="ParcelBuild" BeforeTargets="Build"> | ||||
| 		<Exec Command="npm run build" /> | ||||
| 	</Target> | ||||
| </Project> | ||||
|  | ||||
| @ -2,3 +2,60 @@ | ||||
| @model EnotaryoPH.Web.Pages.Notary.Dashboard.DashboardModel | ||||
| @{ | ||||
| } | ||||
| 
 | ||||
| @section Head { | ||||
|     <link href="~/lib/fontawesome-free-6.7.1-web/css/all.min.css" rel="stylesheet" /> | ||||
| } | ||||
| 
 | ||||
| <section class="my-5"> | ||||
|     <div class="container"> | ||||
|         <div class="row"> | ||||
|             <div class="col-2 col-sm-1 col-md-3 g-0"> | ||||
|                 <div class="sidemenu"> | ||||
|                     <div class="align-items-center sidemenu__menuitem"><a class="d-flex flex-grow-1 justify-content-center align-items-center justify-content-md-start p-1 text-decoration-none" href="#available-transactions"><i class="far fa-check-circle fs-4 text-success sidemenu__menuitem__icon" style="padding: 5px;"></i><span class="d-none d-md-inline-block ms-1 sidemenu__menuitem__text">Available</span></a></div> | ||||
|                     <div class="align-items-center sidemenu__menuitem"><a class="d-flex flex-grow-1 justify-content-center align-items-center justify-content-md-start p-1 text-decoration-none" href="#incomplete-docs"><i class="far fa-clock fs-4 text-warning sidemenu__menuitem__icon" style="padding: 5px;"></i><span class="d-none d-md-inline-block ms-1 sidemenu__menuitem__text">Incomplete</span></a></div> | ||||
|                     <div class="align-items-center sidemenu__menuitem"><a class="d-flex flex-grow-1 justify-content-center align-items-center justify-content-md-start p-1 text-decoration-none" href="#identification-docs"><i class="far fa-address-card fs-4 text-dark sidemenu__menuitem__icon" style="padding: 5px;"></i><span class="d-none d-md-inline-block ms-1 sidemenu__menuitem__text">Identification Docs</span></a></div> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="col g-0 mx-2"> | ||||
|                 <div id="available-transactions" class="row g-0 mb-5"> | ||||
|                     <div class="col"> | ||||
|                         <h3>Available Transactions</h3> | ||||
|                         <div class="table-responsive"> | ||||
|                             <table class="table"> | ||||
|                                 <thead> | ||||
|                                     <tr> | ||||
|                                         <th>Type</th> | ||||
|                                         <th>Date</th> | ||||
|                                         <th>Link</th> | ||||
|                                     </tr> | ||||
|                                 </thead> | ||||
|                                 <tbody> | ||||
| @*                                     @if (Model.CompletedDocuments.Count > 0) | ||||
|                                     { | ||||
|                                         @foreach (var item in Model.CompletedDocuments) | ||||
|                                         { | ||||
|                                             <tr> | ||||
|                                                 <td>@item.Type</td> | ||||
|                                                 <td>@item.Date.ToShortDateString()</td> | ||||
|                                                 <td><a href="@item.Link">View</a></td> | ||||
|                                             </tr> | ||||
|                                         } | ||||
|                                     } | ||||
|                                     else | ||||
|                                     { | ||||
|                                         <tr> | ||||
|                                             <td colspan="3"> | ||||
|                                                 No records to display. | ||||
|                                             </td> | ||||
|                                         </tr> | ||||
|                                     } *@ | ||||
|                                 </tbody> | ||||
|                             </table> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| </section> | ||||
| @ -33,7 +33,7 @@ namespace EnotaryoPH.Web.Pages.Participant.Registration | ||||
|                 return NotFound(); | ||||
|             } | ||||
| 
 | ||||
|             var existingUser = _notaryoDBContext.Users.FirstOrDefault(e => e.Email.ToLower() == signatory.Email.ToLower()); | ||||
|             var existingUser = _notaryoDBContext.Users.FirstOrDefault(e => e.Email.Equals(signatory.Email, StringComparison.CurrentCultureIgnoreCase)); | ||||
|             if (existingUser != null) | ||||
|             { | ||||
|                 signatory.Status = nameof(SignatoryStatus.Registered); | ||||
|  | ||||
| @ -1,4 +1,160 @@ | ||||
| @page | ||||
| @page "{Transaction_UID}" | ||||
| @using System.Text.Json | ||||
| @model EnotaryoPH.Web.Pages.Participant.VideoCall.RoomModel | ||||
| @{ | ||||
| } | ||||
| 
 | ||||
| @section Head { | ||||
|     <style> | ||||
|         .video-container { | ||||
|             position: relative; | ||||
|             padding-bottom: 56.25%; /* 16:9 aspect ratio */ | ||||
|             background: #000; | ||||
|             border: 2px solid #444; | ||||
| 
 | ||||
|             width: 100%; | ||||
|             overflow: hidden; | ||||
|         } | ||||
| 
 | ||||
|         .video-container video { | ||||
|             position: absolute; | ||||
|             top: 0; | ||||
|             left: 0; | ||||
|             width: 100%; | ||||
|             height: 100%; | ||||
|             object-fit: cover; /* This will maintain the video's original aspect ratio and crop if necessary */ | ||||
|         } | ||||
| 
 | ||||
|         .video-element { | ||||
|             position: absolute; | ||||
|             width: 100%; | ||||
|             height: 100%; | ||||
|             object-fit: cover; | ||||
|         } | ||||
| 
 | ||||
|         .participant-name { | ||||
|             position: absolute; | ||||
|             bottom: 10px; | ||||
|             left: 10px; | ||||
|             color: white; | ||||
|             background: rgba(0,0,0,0.5); | ||||
|             padding: 2px 8px; | ||||
|             border-radius: 4px; | ||||
|             z-index: 1; | ||||
|         } | ||||
| 
 | ||||
|         .controls { | ||||
|             position: fixed; | ||||
|             bottom: 20px; | ||||
|             left: 50%; | ||||
|             /* transform: translateX(-50%); */ | ||||
|             background: rgba(0,0,0,0.8); | ||||
|             padding: 10px; | ||||
|             border-radius: 20px; | ||||
|         } | ||||
| 
 | ||||
|         .participant-col { | ||||
|             transition: all 0.3s ease; | ||||
| 
 | ||||
|             display:flex; | ||||
|             justify-content: center; | ||||
|             align-items: center; | ||||
|         } | ||||
|     </style> | ||||
| } | ||||
| 
 | ||||
| <div class="container-fluid py-3" id="videoGrid-container"> | ||||
|     <div class="row g-2" id="videoGrid"> | ||||
|     </div> | ||||
| </div> | ||||
| 
 | ||||
| <template id="templateVideo"> | ||||
|     <div class="video-container bg-light"> | ||||
|         <div class="participant-name">Participant Name</div> | ||||
|     </div> | ||||
| </template> | ||||
| 
 | ||||
| <input type="hidden" value="@JsonSerializer.Serialize(Model.Participants)" /> | ||||
| 
 | ||||
| @section Scripts { | ||||
|     <script type="text/javascript" src="~/dist/js/_Jfa.js"></script> | ||||
|     <script> | ||||
| 
 | ||||
|         let participants = JSON.parse('@Html.Raw(JsonSerializer.Serialize(Model.Participants))'); | ||||
|         function updateGrid() { | ||||
|             const videoGrid = document.getElementById('videoGrid'); | ||||
|             videoGrid.innerHTML = ''; | ||||
| 
 | ||||
|             participants.forEach((participant, index) => { | ||||
|                 const col = document.createElement('div'); | ||||
|                 col.className = 'participant-col'; | ||||
|                 var tmpl = document.getElementById("templateVideo").cloneNode(true).content; | ||||
|                 let vidcontainer = tmpl.querySelector(".video-container") | ||||
|                 if (vidcontainer) { | ||||
|                     vidcontainer.id = participant.Id; | ||||
|                     vidcontainer.classList.add(participant.Id); | ||||
|                     vidcontainer.classList.add(participant.Type == 'Notary' ? 'local-video-container' : 'remote-video-container'); | ||||
|                 } | ||||
|                 let participantName = tmpl.querySelector(".participant-name") | ||||
|                 if (participantName) { | ||||
|                     participantName.textContent = participant.DisplayName; | ||||
|                 } | ||||
|                 col.appendChild(tmpl); | ||||
|                 videoGrid.appendChild(col); | ||||
|             }); | ||||
| 
 | ||||
|             // Dynamically adjust grid columns based on participant count | ||||
|             const count = participants.length; | ||||
|             const cols = count <= 2 ? 'col-12 col-sm-12 offset-md-1 col-md-10 offset-lg-0 col-lg-6' : | ||||
|                         count <= 4 ? 'col-6 col-sm-6 col-md-6 col-lg-6 col-xl-6' : | ||||
|                         count <= 8 ? 'col-6 col-md-6 col-lg-4 col-xl-4' : | ||||
|                         count <= 9 ? 'col-4' : | ||||
|                         'col-6 col-sm-4 col-lg-3'; | ||||
| 
 | ||||
|             document.querySelectorAll('.participant-col').forEach(el => { | ||||
|                 el.className = `participant-col ${cols}`; | ||||
|             }); | ||||
| 
 | ||||
|             const fluid = count <= 2 ? 'container-fluid' : | ||||
|                 count <= 8 ? 'container-xxl' : | ||||
|                 'container-xl' | ||||
|             document.getElementById('videoGrid-container').className = `${fluid} py-3`; | ||||
|         } | ||||
| 
 | ||||
|         async function initVideoCall() { | ||||
|             let userAccessToken = '@Model.CommunicationUserToken'; | ||||
|             const videoCall = jfa.communication.videocall; | ||||
|             let options = { | ||||
|                 onCreateLocalVideoStream: function(el) { | ||||
|                     if (el) { | ||||
|                         el.style['transform'] = ''; | ||||
|                         let notary = participants.find(p => p.RoomUserID == '@Model.CommunicationUserId'); | ||||
|                         document.getElementById(notary.Id).appendChild(el); | ||||
|                     } | ||||
|                 }, | ||||
|                 remoteVideoIsAvailableChanged: function(e) { | ||||
|                     let participant = participants.find(p => p.RoomUserID == e.participantId); | ||||
|                     if (participant) { | ||||
|                         e.el.querySelector('video').style['object-fit'] = 'cover'; | ||||
|                         document.getElementById(participant.Id).appendChild(e.el); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             await videoCall.init(userAccessToken, options); | ||||
|             videoCall.joinRoom('@Model.CommunicationRoomId'); | ||||
|         } | ||||
| 
 | ||||
|         if (document.readyState !== 'loading') { | ||||
|             init(); | ||||
|         } else { | ||||
|             document.addEventListener('DOMContentLoaded', init); | ||||
|         } | ||||
| 
 | ||||
|         async function init() { | ||||
|             updateGrid(); | ||||
|             window.addEventListener('resize', updateGrid); | ||||
| 
 | ||||
|             await initVideoCall(); | ||||
|         } | ||||
|     </script> | ||||
| } | ||||
|  | ||||
| @ -1,3 +1,9 @@ | ||||
| using Azure; | ||||
| using Azure.Communication; | ||||
| using Azure.Communication.Identity; | ||||
| using Azure.Communication.Rooms; | ||||
| using EnotaryoPH.Data; | ||||
| using EnotaryoPH.Data.Entities; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
| using Microsoft.AspNetCore.Mvc.RazorPages; | ||||
| 
 | ||||
| @ -5,8 +11,138 @@ namespace EnotaryoPH.Web.Pages.Participant.VideoCall | ||||
| { | ||||
|     public class RoomModel : PageModel | ||||
|     { | ||||
|         public void OnGet() | ||||
|         private const string CONNECTION_STRING = "endpoint=https://comm-enotario.asiapacific.communication.azure.com/;accesskey=yqxq5mRByG8aPwZBiT/KFfx6Rr16RR280dC3GVkGaFVNXGk91sIDy04j5jrZfjDZAvdn0fjtyF0kGJPXMGgfKg=="; | ||||
|         private readonly ICurrentUserService _currentUserService; | ||||
|         private readonly NotaryoDBContext _dbContext; | ||||
|         private readonly ISession _session; | ||||
|         private readonly CommunicationUserIdentifier? _user1; | ||||
|         private readonly CommunicationUserIdentifier? _user2; | ||||
|         private CommunicationIdentityClient? _communicationIdentityClient; | ||||
|         private LawyerVideoConferenceSchedule _LawyerVideoConferenceSchedule; | ||||
|         private Transaction _Transaction; | ||||
| 
 | ||||
|         public RoomModel(IHttpContextAccessor httpContextAccessor, ICurrentUserService currentUserService, NotaryoDBContext dbContext) | ||||
|         { | ||||
|             _session = httpContextAccessor.HttpContext.Session; | ||||
|             _currentUserService = currentUserService; | ||||
|             _dbContext = dbContext; | ||||
|         } | ||||
| 
 | ||||
|         public async Task<IActionResult> OnGetAsync() | ||||
|         { | ||||
|             _Transaction = _dbContext.Transactions | ||||
|                 .Include(t => t.TransactionSignatories) | ||||
|                 .FirstOrDefault(t => t.Transaction_UID == Transaction_UID); | ||||
|             if (_Transaction == null) | ||||
|             { | ||||
|                 return NotFound(); | ||||
|             } | ||||
| 
 | ||||
|             _LawyerVideoConferenceSchedule = _dbContext.LawyerVideoConferenceSchedules | ||||
|                 .Include(meeting => meeting.LawyerVideoConferenceParticipants) | ||||
|                 .ThenInclude(p => p.Participant) | ||||
|                 .Include(meeting => meeting.Lawyer) | ||||
|                 .ThenInclude(l => l.User) | ||||
|                 .FirstOrDefault(meeting => meeting.TransactionID == _Transaction.TransactionID); | ||||
| 
 | ||||
|             if (ShouldCreateRoom()) | ||||
|             { | ||||
|                 _LawyerVideoConferenceSchedule ??= new LawyerVideoConferenceSchedule | ||||
|                 { | ||||
|                     LawyerVideoConferenceSchedule_UID = Guid.CreateVersion7(), | ||||
|                     CreatedOn = DateTime.UtcNow, | ||||
|                     LawyerID = _Transaction.LawyerID.GetValueOrDefault(), | ||||
|                     TransactionID = _Transaction.TransactionID, | ||||
|                 }; | ||||
|                 _LawyerVideoConferenceSchedule.MeetingDate = DateTime.UtcNow; | ||||
|                 _LawyerVideoConferenceSchedule.Status = nameof(VideoConferenceStatus.New); | ||||
| 
 | ||||
|                 var roomParticipants = new List<RoomParticipant>(); | ||||
|                 foreach (var participant in _LawyerVideoConferenceSchedule.LawyerVideoConferenceParticipants) | ||||
|                 { | ||||
|                     var attendee = await CommunicationIdentityClient.CreateUserAsync(); | ||||
|                     roomParticipants.Add(new RoomParticipant(attendee) { Role = ParticipantRole.Attendee }); | ||||
|                     participant.MeetingRoomTokenID = await GetTokenResponse(attendee); | ||||
|                     participant.MeetingRoomUserID = attendee.Value.Id; | ||||
|                 } | ||||
| 
 | ||||
|                 var presenter = await CommunicationIdentityClient.CreateUserAsync(); | ||||
|                 roomParticipants.Add(new RoomParticipant(presenter) { Role = ParticipantRole.Presenter }); | ||||
|                 _LawyerVideoConferenceSchedule.MeetingRoomTokenID = await GetTokenResponse(presenter); | ||||
|                 _LawyerVideoConferenceSchedule.MeetingRoomUserID = presenter.Value.Id; | ||||
| 
 | ||||
|                 var roomsClient = new RoomsClient(CONNECTION_STRING); | ||||
|                 CommunicationRoom room = await roomsClient.CreateRoomAsync(DateTime.Now, DateTime.Now.AddHours(2), roomParticipants); | ||||
| 
 | ||||
|                 _LawyerVideoConferenceSchedule.MeetingRoomID = room.Id; | ||||
| 
 | ||||
|                 if (_LawyerVideoConferenceSchedule.LawyerVideoConferenceScheduleID == 0) | ||||
|                 { | ||||
|                     _dbContext.Add(_LawyerVideoConferenceSchedule); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     _dbContext.Update(_LawyerVideoConferenceSchedule); | ||||
|                 } | ||||
|                 _dbContext.SaveChanges(); | ||||
|             } | ||||
| 
 | ||||
|             var currentUser = _dbContext.Users.FirstOrDefault(u => u.User_UID == _currentUserService.GetUser_UID()); | ||||
|             CommunicationUserToken = currentUser.Role == nameof(UserType.Notary) | ||||
|                 ? _LawyerVideoConferenceSchedule.MeetingRoomTokenID | ||||
|                 : _LawyerVideoConferenceSchedule.LawyerVideoConferenceParticipants.First(u => u.ParticipantID == currentUser.UserID).MeetingRoomTokenID; | ||||
|             CommunicationRoomId = _LawyerVideoConferenceSchedule.MeetingRoomID; | ||||
|             CommunicationUserId = currentUser.Role == nameof(UserType.Notary) | ||||
|                 ? _LawyerVideoConferenceSchedule.MeetingRoomUserID | ||||
|                 : _LawyerVideoConferenceSchedule.LawyerVideoConferenceParticipants.First(u => u.ParticipantID == currentUser.UserID).MeetingRoomUserID; | ||||
| 
 | ||||
| 
 | ||||
|             return Page(); | ||||
|         } | ||||
| 
 | ||||
|         private static string GetHashCode(string stringValue) => stringValue.GetHashCode().ToString("X8"); | ||||
| 
 | ||||
|         private async Task<string> GetTokenResponse(Response<CommunicationUserIdentifier> user) | ||||
|         { | ||||
|             var tokenResponse = await CommunicationIdentityClient.GetTokenAsync(user, new[] { CommunicationTokenScope.VoIP }); | ||||
|             return tokenResponse.Value.Token; | ||||
|         } | ||||
| 
 | ||||
|         private bool ShouldCreateRoom() => _LawyerVideoConferenceSchedule == null || DateTime.UtcNow.Subtract(_LawyerVideoConferenceSchedule.MeetingDate).TotalHours > 1 || _LawyerVideoConferenceSchedule.LawyerVideoConferenceParticipants.Any(p => string.IsNullOrEmpty(p.MeetingRoomTokenID)); | ||||
| 
 | ||||
|         public string CommunicationRoomId { get; private set; } | ||||
|         public string CommunicationUserToken { get; private set; } | ||||
|         public string CommunicationUserId { get; set; } | ||||
| 
 | ||||
|         public List<RoomParticipantViewModel> Participants | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 var signatoryTypes = _Transaction.TransactionSignatories.Where(t => t.UserID > 0).ToDictionary(k => k.UserID, v => v.Type); | ||||
|                 var dic = _LawyerVideoConferenceSchedule.LawyerVideoConferenceParticipants.ConvertAll(p => new RoomParticipantViewModel | ||||
|                 { | ||||
|                     Id = p.LawyerVideoConferenceParticipant_UID.ToString(), | ||||
|                     DisplayName = $"{p.Participant.Firstname} {p.Participant.Lastname}", | ||||
|                     RoomUserID = p.MeetingRoomUserID, | ||||
|                     Type = signatoryTypes.GetValueOrDefault(p.ParticipantID, nameof(UserType.Principal)) | ||||
|                 }); | ||||
| 
 | ||||
|                 var host = _LawyerVideoConferenceSchedule.Lawyer.User; | ||||
|                 dic.Add(new RoomParticipantViewModel | ||||
|                 { | ||||
|                     DisplayName = $"{host.Firstname} {host.Lastname}", | ||||
|                     Id = Guid.Empty.ToString(), | ||||
|                     RoomUserID = _LawyerVideoConferenceSchedule.MeetingRoomUserID, | ||||
|                     Type = nameof(UserType.Notary) | ||||
|                 }); | ||||
| 
 | ||||
|                 return dic; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         [BindProperty(SupportsGet = true)] | ||||
|         public Guid Transaction_UID { get; set; } | ||||
| 
 | ||||
|         private CommunicationIdentityClient CommunicationIdentityClient => _communicationIdentityClient ??= new CommunicationIdentityClient(CONNECTION_STRING); | ||||
|     } | ||||
| } | ||||
| } | ||||
| @ -0,0 +1,10 @@ | ||||
| namespace EnotaryoPH.Web.Pages.Participant.VideoCall | ||||
| { | ||||
|     public class RoomParticipantViewModel | ||||
|     { | ||||
|         public string DisplayName { get; set; } | ||||
|         public string Id { get; set; } | ||||
|         public string RoomUserID { get; set; } | ||||
|         public string Type { get; set; } | ||||
|     } | ||||
| } | ||||
| @ -35,7 +35,7 @@ | ||||
|                     </ul> | ||||
|                     @if (User.Identity?.IsAuthenticated ?? false) | ||||
|                     { | ||||
|                         <a class="btn btn-primary ms-md-2" role="button" href="/Login?handler=Logout">Logout</a> | ||||
|                         <a class="btn btn-primary ms-md-2" role="button" href="/Login?handler=Logout" data-bs-toggle="tooltip" data-bss-tooltip data-bs-placement="bottom" title="@User.Identity.Name">Logout</a> | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|  | ||||
| @ -1,23 +1,23 @@ | ||||
| { | ||||
|   "$schema": "https://json.schemastore.org/launchsettings.json", | ||||
|   "profiles": { | ||||
|     "http": { | ||||
|       "commandName": "Project", | ||||
|       "dotnetRunMessages": true, | ||||
|       "launchBrowser": true, | ||||
|       "applicationUrl": "http://localhost:5199", | ||||
|       "environmentVariables": { | ||||
|         "ASPNETCORE_ENVIRONMENT": "Development" | ||||
|       } | ||||
|     }, | ||||
|     "https": { | ||||
|       "commandName": "Project", | ||||
|       "dotnetRunMessages": true, | ||||
|       "launchBrowser": true, | ||||
|       "applicationUrl": "https://localhost:7121;http://localhost:5199", | ||||
|       "environmentVariables": { | ||||
|         "ASPNETCORE_ENVIRONMENT": "Development" | ||||
|       } | ||||
|     "$schema": "https://json.schemastore.org/launchsettings.json", | ||||
|     "profiles": { | ||||
|         "http": { | ||||
|             "commandName": "Project", | ||||
|             "dotnetRunMessages": true, | ||||
|             "launchBrowser": true, | ||||
|             "applicationUrl": "http://0.0.0.0:5199", | ||||
|             "environmentVariables": { | ||||
|                 "ASPNETCORE_ENVIRONMENT": "Development" | ||||
|             } | ||||
|         }, | ||||
|         "https": { | ||||
|             "commandName": "Project", | ||||
|             "dotnetRunMessages": true, | ||||
|             "launchBrowser": true, | ||||
|             "applicationUrl": "https://0.0.0.0:7121;http://0.0.0.0:5199", | ||||
|             "environmentVariables": { | ||||
|                 "ASPNETCORE_ENVIRONMENT": "Development" | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| } | ||||
| @ -1,33 +0,0 @@ | ||||
| const gulp = require("gulp"), | ||||
|   concat = require('gulp-concat'), | ||||
|   del = require('del'), | ||||
|   sass = require('gulp-sass')(require('sass')), | ||||
|   rename = require("gulp-rename"), | ||||
|   uglify = require('gulp-uglify'); | ||||
| 
 | ||||
| const bundles = { | ||||
|   jfa: { | ||||
|     outputFileName: "wwwroot/dist/js/jfa.js", | ||||
|     inputFiles: [ | ||||
|       "Assets/js/_Jfa.js", | ||||
|       "Assets/js/Page/_Page.js", | ||||
|       "Assets/js/Utilities/Routing/_Routing.js", | ||||
|       "Assets/js/Utilities/Element/_Element.js", | ||||
|       "Assets/js/Utilities/Request/_Request.js", | ||||
|       "Assets/js/Components/Dialog/_Dialog.js" | ||||
|     ] | ||||
|   }, | ||||
| }; | ||||
| 
 | ||||
| gulp.task('bundle:jfa', function () { | ||||
|   return gulp.src(bundles.jfa.inputFiles, { base: "." }) | ||||
|     .pipe(concat(bundles.jfa.outputFileName)) | ||||
|     .pipe(gulp.dest(".")) | ||||
|     .pipe(uglify()) | ||||
|     .pipe(rename({ suffix: '.min' })) | ||||
|     .pipe(gulp.dest(".")); | ||||
| }); | ||||
| 
 | ||||
| gulp.task('default', gulp.series( | ||||
|   gulp.parallel(['bundle:jfa']) | ||||
| )); | ||||
							
								
								
									
										4943
									
								
								EnotaryoPH/EnotaryoPH.Web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4943
									
								
								EnotaryoPH/EnotaryoPH.Web/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -1,18 +1,30 @@ | ||||
| { | ||||
|     "name": "jfa home", | ||||
|     "name": "eNotaryo", | ||||
|     "version": "1.0.0", | ||||
|     "description": "JFA Websites", | ||||
|     "main": "index.js", | ||||
|     "description": "Electronic Notarization Platform", | ||||
|     "author": "Jose Aquino Jr", | ||||
|     "license": "MIT", | ||||
|     "browser": "wwwroot/dist/jfa.js", | ||||
|     "targets": { | ||||
|         "default": { | ||||
|             "context": "browser", | ||||
|             "outputFormat": "global", | ||||
|             "scopeHoist": false | ||||
|         } | ||||
|     }, | ||||
|     "browserslist": "> 0.5%", | ||||
|     "scripts": { | ||||
|         "clean": "rimraf wwwroot/dist", | ||||
|         "build:jfa": "npm run clean && parcel build Assets/js/_Jfa.js --dist-dir wwwroot/dist/js --no-source-maps --no-optimize && parcel build Assets/js/_Jfa.js --dist-dir wwwroot/dist/js2 --no-source-maps", | ||||
|         "build": "npm run build:jfa", | ||||
|         "start": "parcel watch Assets/js/_Jfa.js --dist-dir wwwroot/dist/js --no-source-maps" | ||||
|     }, | ||||
|     "devDependencies": { | ||||
|         "del": "6.1.1", | ||||
|         "gulp": "^5.0.0", | ||||
|         "gulp-concat": "^2.6.1", | ||||
|         "gulp-rename": "^2.0.0", | ||||
|         "gulp-sass": "^5.1.0", | ||||
|         "gulp-uglify": "^3.0.2", | ||||
|         "merge-stream": "^2.0.0", | ||||
|         "sass": "^1.54.9" | ||||
|         "parcel": "^2.13.3", | ||||
|         "rimraf": "^6.0.1" | ||||
|     }, | ||||
|     "dependencies": { | ||||
|         "@azure/communication-calling": "^1.33.2", | ||||
|         "@azure/communication-common": "^2.3.1" | ||||
|     } | ||||
| } | ||||
| } | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @ -7,6 +7,13 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnotaryoPH.Web", "EnotaryoP | ||||
| EndProject | ||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnotaryoPH.Data", "EnotaryoPH.Data\EnotaryoPH.Data.csproj", "{7B06785D-1B94-472B-885C-3C2F8834438B}" | ||||
| EndProject | ||||
| Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D9FA3452-4AC7-4628-8531-4C6BC344427B}" | ||||
| 	ProjectSection(SolutionItems) = preProject | ||||
| 		..\.gitignore = ..\.gitignore | ||||
| 		..\azure-pipelines.yml = ..\azure-pipelines.yml | ||||
| 		..\README.md = ..\README.md | ||||
| 	EndProjectSection | ||||
| EndProject | ||||
| Global | ||||
| 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
| 		Debug|Any CPU = Debug|Any CPU | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user