2022-01-28 13:00:11 -08:00
import $ from 'jquery' ;
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' ;
2021-01-21 14:51:52 +00:00
2022-01-28 13:00:11 -08:00
const { appSubUrl , csrfToken , notificationSettings , enableTimeTracking } = 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 ,
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.
const currSeconds = $ ( '.stopwatch-time' ) . attr ( 'data-seconds' ) ;
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
const worker = new SharedWorker ( ` ${ _ _webpack _public _path _ _ } js/eventsource.sharedworker.js ` , 'notification-worker' ) ;
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 ( ) ;
2021-10-21 15:37:43 +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 ( ) {
const data = await $ . ajax ( {
type : 'GET' ,
2022-04-08 02:59:56 +08:00
url : ` ${ appSubUrl } /user/stopwatches ` ,
2021-10-21 15:37:43 +08:00
headers : { 'X-Csrf-Token' : csrfToken } ,
2021-01-21 14:51:52 +00:00
} ) ;
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 ] ;
const btnEl = $ ( '.active-stopwatch-trigger' ) ;
if ( ! watch ) {
2022-08-04 03:58:27 +08:00
clearStopwatchTimer ( ) ;
2021-01-21 14:51:52 +00:00
btnEl . addClass ( 'hidden' ) ;
} 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 } ` ;
2021-01-21 14:51:52 +00:00
$ ( '.stopwatch-link' ) . attr ( 'href' , issueUrl ) ;
$ ( '.stopwatch-commit' ) . attr ( 'action' , ` ${ issueUrl } /times/stopwatch/toggle ` ) ;
$ ( '.stopwatch-cancel' ) . attr ( 'action' , ` ${ issueUrl } /times/stopwatch/cancel ` ) ;
$ ( '.stopwatch-issue' ) . text ( ` ${ repo _owner _name } / ${ repo _name } # ${ issue _index } ` ) ;
2022-08-04 03:58:27 +08:00
updateStopwatchTime ( seconds ) ;
2021-01-21 14:51:52 +00:00
btnEl . removeClass ( 'hidden' ) ;
}
return ! ! data . length ;
}
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 ( ) ;
const $stopwatch = $ ( '.stopwatch-time' ) ;
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 } ) ;
2022-08-04 03:58:27 +08:00
$stopwatch . text ( dur ) ;
} ;
updateUi ( ) ;
updateTimeIntervalId = setInterval ( updateUi , 1000 ) ;
2021-01-21 14:51:52 +00:00
}