|
|
@ -19,6 +19,8 @@ dayjs.extend(dayjsRelativeTime); |
|
|
|
|
|
|
|
export class TimeTrackerApp extends DtpApp { |
|
|
|
|
|
|
|
static get SCREENSHOT_INTERVAL ( ) { return 1000 * 60 * 10; } |
|
|
|
|
|
|
|
static get SFX_TRACKER_START ( ) { return 'tracker-start'; } |
|
|
|
static get SFX_TRACKER_UPDATE ( ) { return 'tracker-update'; } |
|
|
|
static get SFX_TRACKER_STOP ( ) { return 'tracker-stop'; } |
|
|
@ -36,7 +38,7 @@ export class TimeTrackerApp extends DtpApp { |
|
|
|
|
|
|
|
this.currentSessionStartTime = null; |
|
|
|
this.currentSessionDuration = document.querySelector('#current-session-duration'); |
|
|
|
this.currentSessionBillable = document.querySelector('#current-session-billable'); |
|
|
|
this.currentSessionTimeRemaining = document.querySelector('#time-remaining'); |
|
|
|
|
|
|
|
window.addEventListener('dtp-load', this.onDtpLoad.bind(this)); |
|
|
|
window.addEventListener('focus', this.onWindowFocus.bind(this)); |
|
|
@ -57,8 +59,8 @@ export class TimeTrackerApp extends DtpApp { |
|
|
|
|
|
|
|
await this.connect({ |
|
|
|
mode: 'User', |
|
|
|
onSocketConnect: this.onChatSocketConnect.bind(this), |
|
|
|
onSocketDisconnect: this.onChatSocketDisconnect.bind(this), |
|
|
|
onSocketConnect: this.onTrackerSocketConnect.bind(this), |
|
|
|
onSocketDisconnect: this.onTrackerSocketDisconnect.bind(this), |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
@ -142,16 +144,17 @@ export class TimeTrackerApp extends DtpApp { |
|
|
|
this.dragFeedback.classList.remove('feedback-active'); |
|
|
|
} |
|
|
|
|
|
|
|
async onChatSocketConnect (socket) { |
|
|
|
async onTrackerSocketConnect (socket) { |
|
|
|
this.log.debug('onSocketConnect', 'attaching socket events'); |
|
|
|
socket.on('system-message', this.onSystemMessage.bind(this)); |
|
|
|
socket.on('session-control', this.onSessionControl.bind(this)); |
|
|
|
|
|
|
|
if (dtp.task) { |
|
|
|
await this.socket.joinChannel(dtp.task._id, 'Task'); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
async onChatSocketDisconnect (socket) { |
|
|
|
async onTrackerSocketDisconnect (socket) { |
|
|
|
this.log.debug('onSocketDisconnect', 'detaching socket events'); |
|
|
|
socket.off('system-message', this.onSystemMessage.bind(this)); |
|
|
|
} |
|
|
@ -162,6 +165,30 @@ export class TimeTrackerApp extends DtpApp { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
async onSessionControl (message) { |
|
|
|
const activityToggle = document.querySelector(''); |
|
|
|
|
|
|
|
switch (message.cmd) { |
|
|
|
case 'end-session': |
|
|
|
try { |
|
|
|
await this.closeTaskSession(); |
|
|
|
activityToggle.checked = false; |
|
|
|
} catch (error) { |
|
|
|
this.log.error('onSessionControl', 'failed to close task work session', { error }); |
|
|
|
return; |
|
|
|
} |
|
|
|
break; |
|
|
|
|
|
|
|
default: |
|
|
|
this.log.error('onSessionControl', 'invalid command received', { cmd: message.cmd }); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if (message.displayList) { |
|
|
|
this.displayEngine.executeDisplayList(message.displayList); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
async confirmNavigation (event) { |
|
|
|
const target = event.currentTarget || event.target; |
|
|
|
event.preventDefault(); |
|
|
@ -171,10 +198,11 @@ export class TimeTrackerApp extends DtpApp { |
|
|
|
const hrefTarget = target.getAttribute('target'); |
|
|
|
const text = target.textContent; |
|
|
|
const whitelist = [ |
|
|
|
'digitaltelepresence.com', |
|
|
|
'www.digitaltelepresence.com', |
|
|
|
'chat.digitaltelepresence.com', |
|
|
|
'digitaltelepresence.com', |
|
|
|
'sites.digitaltelepresence.com', |
|
|
|
'tracker.digitaltelepresence.com', |
|
|
|
'www.digitaltelepresence.com', |
|
|
|
]; |
|
|
|
try { |
|
|
|
const url = new URL(href); |
|
|
@ -189,6 +217,22 @@ export class TimeTrackerApp extends DtpApp { |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
async performSessionNavigation (event) { |
|
|
|
const target = event.currentTarget || event.target; |
|
|
|
|
|
|
|
event.preventDefault(); |
|
|
|
event.stopPropagation(); |
|
|
|
|
|
|
|
const href = target.getAttribute('href'); |
|
|
|
const hrefTarget = target.getAttribute('target'); |
|
|
|
if (this.taskSession || (hrefTarget && (hrefTarget.length > 0))) { |
|
|
|
return window.open(href, hrefTarget); |
|
|
|
} |
|
|
|
|
|
|
|
window.location = href; |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
async generateOtpQR (canvas, keyURI) { |
|
|
|
QRCode.toCanvas(canvas, keyURI); |
|
|
|
} |
|
|
@ -435,7 +479,7 @@ export class TimeTrackerApp extends DtpApp { |
|
|
|
this.taskSession = json.session; |
|
|
|
this.currentSessionStartTime = new Date(); |
|
|
|
|
|
|
|
this.screenshotInterval = setInterval(this.captureScreenshot.bind(this), 1000 * 60 * 10); |
|
|
|
this.screenshotInterval = setInterval(this.captureScreenshot.bind(this), TimeTrackerApp.SCREENSHOT_INTERVAL); |
|
|
|
this.sessionDisplayUpdateInterval = setInterval(this.updateSessionDisplay.bind(this), 250); |
|
|
|
this.currentSessionDuration.classList.add('uk-text-success'); |
|
|
|
|
|
|
@ -475,18 +519,6 @@ export class TimeTrackerApp extends DtpApp { |
|
|
|
this.captureStream = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: false }); |
|
|
|
this.capturePreview.srcObject = this.captureStream; |
|
|
|
this.capturePreview.play(); |
|
|
|
|
|
|
|
const tracks = this.captureStream.getVideoTracks(); |
|
|
|
const constraints = tracks[0].getSettings(); |
|
|
|
|
|
|
|
this.log.info('startScreenCapture', 'creating capture canvas', { |
|
|
|
width: constraints.width, |
|
|
|
height: constraints.height, |
|
|
|
}); |
|
|
|
this.captureCanvas = document.createElement('canvas'); |
|
|
|
this.captureCanvas.width = constraints.width; |
|
|
|
this.captureCanvas.height = constraints.height; |
|
|
|
this.captureContext = this.captureCanvas.getContext('2d'); |
|
|
|
} |
|
|
|
|
|
|
|
async stopScreenCapture ( ) { |
|
|
@ -499,20 +531,18 @@ export class TimeTrackerApp extends DtpApp { |
|
|
|
|
|
|
|
this.captureStream.getTracks().forEach(track => track.stop()); |
|
|
|
delete this.captureStream; |
|
|
|
|
|
|
|
if (this.captureContext) { |
|
|
|
delete this.captureContext; |
|
|
|
} |
|
|
|
if (this.captureCanvas) { |
|
|
|
delete this.captureCanvas; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
async updateSessionDisplay ( ) { |
|
|
|
const NOW = new Date(); |
|
|
|
const duration = dayjs(NOW).diff(this.currentSessionStartTime, 'second'); |
|
|
|
this.currentSessionDuration.textContent = numeral(duration).format('HH:MM:SS'); |
|
|
|
this.currentSessionBillable.textContent = numeral(this.taskSession.hourlyRate * (duration / 60 / 60)).format('$0,0.00'); |
|
|
|
|
|
|
|
const timeRemaining = |
|
|
|
this.taskSession.client.hoursLimit - |
|
|
|
((this.taskSession.client.weeklyTotals.timeWorked + duration) / 3600) |
|
|
|
; |
|
|
|
this.currentSessionTimeRemaining.textContent = numeral(timeRemaining).format('0,0.00'); |
|
|
|
} |
|
|
|
|
|
|
|
async captureScreenshot ( ) { |
|
|
@ -520,19 +550,33 @@ export class TimeTrackerApp extends DtpApp { |
|
|
|
return; |
|
|
|
} |
|
|
|
try { |
|
|
|
const tracks = this.captureStream.getVideoTracks(); |
|
|
|
const constraints = tracks[0].getSettings(); |
|
|
|
|
|
|
|
this.log.info('startScreenCapture', 'creating capture canvas', { |
|
|
|
width: constraints.width, |
|
|
|
height: constraints.height, |
|
|
|
}); |
|
|
|
|
|
|
|
const captureCanvas = document.createElement('canvas'); |
|
|
|
captureCanvas.width = constraints.width; |
|
|
|
captureCanvas.height = constraints.height; |
|
|
|
|
|
|
|
const captureContext = captureCanvas.getContext('2d'); |
|
|
|
|
|
|
|
/* |
|
|
|
* Capture the current preview stream frame to the capture canvas |
|
|
|
*/ |
|
|
|
this.captureContext.drawImage( |
|
|
|
captureContext.drawImage( |
|
|
|
this.capturePreview, |
|
|
|
0, 0, |
|
|
|
this.captureCanvas.width, |
|
|
|
this.captureCanvas.height, |
|
|
|
captureCanvas.width, |
|
|
|
captureCanvas.height, |
|
|
|
); |
|
|
|
/* |
|
|
|
* Generate a PNG Blob from the capture canvas |
|
|
|
*/ |
|
|
|
this.captureCanvas.toBlob( |
|
|
|
captureCanvas.toBlob( |
|
|
|
async (blob) => { |
|
|
|
const formData = new FormData(); |
|
|
|
formData.append('image', blob, 'screenshot.png'); |
|
|
@ -547,7 +591,6 @@ export class TimeTrackerApp extends DtpApp { |
|
|
|
this.log.info('captureScreenshot', 'screenshot posted to task session'); |
|
|
|
}, |
|
|
|
'image/png', |
|
|
|
1.0, |
|
|
|
); |
|
|
|
} catch (error) { |
|
|
|
this.log.error('captureScreenshot', 'failed to capture screenshot', { error }); |
|
|
|