2021-01-21 14:51:52 +00:00
import prettyMilliseconds from 'pretty-ms' ;
2022-08-09 14:37:34 +02:00
import { createTippy } from '../modules/tippy.js' ;
2024-02-23 23:19:54 +02:00
import { GET } from '../modules/fetch.js' ;
import { hideElem , showElem } from '../utils/dom.js' ;
2021-01-21 14:51:52 +00:00
2024-02-23 23:19:54 +02:00
const { appSubUrl , notificationSettings , enableTimeTracking , assetVersionEncoded } = window . config ;
2021-01-21 14:51:52 +00:00
2021-11-09 17:27:25 +08:00
export function initStopwatch ( ) {
2021-10-21 15:37:43 +08:00
if ( ! enableTimeTracking ) {
2021-02-19 23:06:56 +00:00
return ;
}
2021-01-21 14:51:52 +00:00
2022-08-09 14:37:34 +02:00
const stopwatchEl = document . querySelector ( '.active-stopwatch-trigger' ) ;
const stopwatchPopup = document . querySelector ( '.active-stopwatch-popup' ) ;
2021-04-05 10:45:01 -06:00
2022-08-09 14:37:34 +02:00
if ( ! stopwatchEl || ! stopwatchPopup ) {
2021-04-05 10:45:01 -06:00
return ;
}
2022-08-09 14:37:34 +02:00
stopwatchEl . removeAttribute ( 'href' ) ; // intended for noscript mode only
2021-01-21 14:51:52 +00:00
2022-08-09 14:37:34 +02:00
createTippy ( stopwatchEl , {
content : stopwatchPopup ,
placement : 'bottom-end' ,
trigger : 'click' ,
maxWidth : 'none' ,
interactive : true ,
2023-01-10 10:53:11 +08:00
hideOnClick : true ,
2021-01-21 14:51:52 +00:00
} ) ;
2022-08-04 03:58:27 +08:00
// global stop watch (in the head_navbar), it should always work in any case either the EventSource or the PeriodicPoller is used.
2024-02-23 23:19:54 +02:00
const currSeconds = document . querySelector ( '.stopwatch-time' ) ? . getAttribute ( 'data-seconds' ) ;
2022-08-04 03:58:27 +08:00
if ( currSeconds ) {
updateStopwatchTime ( currSeconds ) ;
}
let usingPeriodicPoller = false ;
const startPeriodicPoller = ( timeout ) => {
if ( timeout <= 0 || ! Number . isFinite ( timeout ) ) return ;
usingPeriodicPoller = true ;
setTimeout ( ( ) => updateStopwatchWithCallback ( startPeriodicPoller , timeout ) , timeout ) ;
} ;
// if the browser supports EventSource and SharedWorker, use it instead of the periodic poller
if ( notificationSettings . EventSourceUpdateTime > 0 && window . EventSource && window . SharedWorker ) {
2021-02-19 10:05:35 +00:00
// Try to connect to the event source via the shared worker first
2022-08-23 14:58:04 +02:00
const worker = new SharedWorker ( ` ${ _ _webpack _public _path _ _ } js/eventsource.sharedworker.js?v= ${ assetVersionEncoded } ` , 'notification-worker' ) ;
2021-02-19 10:05:35 +00:00
worker . addEventListener ( 'error' , ( event ) => {
2022-08-04 03:58:27 +08:00
console . error ( 'worker error' , event ) ;
2021-02-19 10:05:35 +00:00
} ) ;
2021-08-17 07:32:48 +02:00
worker . port . addEventListener ( 'messageerror' , ( ) => {
2022-08-04 03:58:27 +08:00
console . error ( 'unable to deserialize message' ) ;
2021-08-17 07:32:48 +02:00
} ) ;
2021-02-19 10:05:35 +00:00
worker . port . postMessage ( {
type : 'start' ,
2021-10-21 15:37:43 +08:00
url : ` ${ window . location . origin } ${ appSubUrl } /user/events ` ,
2021-02-19 10:05:35 +00:00
} ) ;
worker . port . addEventListener ( 'message' , ( event ) => {
if ( ! event . data || ! event . data . type ) {
2022-08-04 03:58:27 +08:00
console . error ( 'unknown worker message event' , event ) ;
2021-02-19 10:05:35 +00:00
return ;
}
if ( event . data . type === 'stopwatches' ) {
updateStopwatchData ( JSON . parse ( event . data . data ) ) ;
2022-08-04 03:58:27 +08:00
} else if ( event . data . type === 'no-event-source' ) {
// browser doesn't support EventSource, falling back to periodic poller
if ( ! usingPeriodicPoller ) startPeriodicPoller ( notificationSettings . MinTimeout ) ;
2021-02-19 10:05:35 +00:00
} else if ( event . data . type === 'error' ) {
2022-08-04 03:58:27 +08:00
console . error ( 'worker port event error' , event . data ) ;
2021-02-19 10:05:35 +00:00
} else if ( event . data . type === 'logout' ) {
2021-04-08 00:48:13 +01:00
if ( event . data . data !== 'here' ) {
2021-02-19 10:05:35 +00:00
return ;
}
worker . port . postMessage ( {
type : 'close' ,
} ) ;
worker . port . close ( ) ;
2024-03-02 20:02:34 +08:00
window . location . href = ` ${ appSubUrl } / ` ;
2021-04-04 22:37:50 +01:00
} else if ( event . data . type === 'close' ) {
worker . port . postMessage ( {
type : 'close' ,
} ) ;
worker . port . close ( ) ;
2021-02-19 10:05:35 +00:00
}
} ) ;
worker . port . addEventListener ( 'error' , ( e ) => {
2022-08-04 03:58:27 +08:00
console . error ( 'worker port error' , e ) ;
2021-02-19 10:05:35 +00:00
} ) ;
worker . port . start ( ) ;
window . addEventListener ( 'beforeunload' , ( ) => {
worker . port . postMessage ( {
type : 'close' ,
} ) ;
worker . port . close ( ) ;
} ) ;
return ;
}
2022-08-04 03:58:27 +08:00
startPeriodicPoller ( notificationSettings . MinTimeout ) ;
2021-01-21 14:51:52 +00:00
}
async function updateStopwatchWithCallback ( callback , timeout ) {
const isSet = await updateStopwatch ( ) ;
if ( ! isSet ) {
2021-10-21 15:37:43 +08:00
timeout = notificationSettings . MinTimeout ;
} else if ( timeout < notificationSettings . MaxTimeout ) {
timeout += notificationSettings . TimeoutStep ;
2021-01-21 14:51:52 +00:00
}
callback ( timeout ) ;
}
async function updateStopwatch ( ) {
2024-02-23 23:19:54 +02:00
const response = await GET ( ` ${ appSubUrl } /user/stopwatches ` ) ;
if ( ! response . ok ) {
console . error ( 'Failed to fetch stopwatch data' ) ;
return false ;
}
const data = await response . json ( ) ;
2021-02-19 10:05:35 +00:00
return updateStopwatchData ( data ) ;
}
2021-11-12 20:37:45 +08:00
function updateStopwatchData ( data ) {
2021-01-21 14:51:52 +00:00
const watch = data [ 0 ] ;
2024-02-23 23:19:54 +02:00
const btnEl = document . querySelector ( '.active-stopwatch-trigger' ) ;
2021-01-21 14:51:52 +00:00
if ( ! watch ) {
2022-08-04 03:58:27 +08:00
clearStopwatchTimer ( ) ;
2024-02-23 23:19:54 +02:00
hideElem ( btnEl ) ;
2021-01-21 14:51:52 +00:00
} else {
const { repo _owner _name , repo _name , issue _index , seconds } = watch ;
2021-10-21 15:37:43 +08:00
const issueUrl = ` ${ appSubUrl } / ${ repo _owner _name } / ${ repo _name } /issues/ ${ issue _index } ` ;
2024-02-23 23:19:54 +02:00
document . querySelector ( '.stopwatch-link' ) ? . setAttribute ( 'href' , issueUrl ) ;
document . querySelector ( '.stopwatch-commit' ) ? . setAttribute ( 'action' , ` ${ issueUrl } /times/stopwatch/toggle ` ) ;
document . querySelector ( '.stopwatch-cancel' ) ? . setAttribute ( 'action' , ` ${ issueUrl } /times/stopwatch/cancel ` ) ;
const stopwatchIssue = document . querySelector ( '.stopwatch-issue' ) ;
if ( stopwatchIssue ) stopwatchIssue . textContent = ` ${ repo _owner _name } / ${ repo _name } # ${ issue _index } ` ;
2022-08-04 03:58:27 +08:00
updateStopwatchTime ( seconds ) ;
2024-02-23 23:19:54 +02:00
showElem ( btnEl ) ;
2021-01-21 14:51:52 +00:00
}
2022-10-10 14:02:20 +02:00
return Boolean ( data . length ) ;
2021-01-21 14:51:52 +00:00
}
2022-08-04 03:58:27 +08:00
let updateTimeIntervalId = null ; // holds setInterval id when active
function clearStopwatchTimer ( ) {
if ( updateTimeIntervalId !== null ) {
clearInterval ( updateTimeIntervalId ) ;
updateTimeIntervalId = null ;
}
}
2021-11-12 20:37:45 +08:00
function updateStopwatchTime ( seconds ) {
2021-01-21 14:51:52 +00:00
const secs = parseInt ( seconds ) ;
2022-08-04 03:58:27 +08:00
if ( ! Number . isFinite ( secs ) ) return ;
2021-01-21 14:51:52 +00:00
2022-08-04 03:58:27 +08:00
clearStopwatchTimer ( ) ;
2024-02-23 23:19:54 +02:00
const stopwatch = document . querySelector ( '.stopwatch-time' ) ;
// TODO: replace with <relative-time> similar to how system status up time is shown
2021-01-21 14:51:52 +00:00
const start = Date . now ( ) ;
2022-08-04 03:58:27 +08:00
const updateUi = ( ) => {
2021-01-21 14:51:52 +00:00
const delta = Date . now ( ) - start ;
const dur = prettyMilliseconds ( secs * 1000 + delta , { compact : true } ) ;
2024-02-23 23:19:54 +02:00
if ( stopwatch ) stopwatch . textContent = dur ;
2022-08-04 03:58:27 +08:00
} ;
updateUi ( ) ;
updateTimeIntervalId = setInterval ( updateUi , 1000 ) ;
2021-01-21 14:51:52 +00:00
}