You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

stopwatch.js 4.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. import prettyMilliseconds from 'pretty-ms';
  2. const {AppSubUrl, csrf, NotificationSettings, EnableTimetracking} = window.config;
  3. let updateTimeInterval = null; // holds setInterval id when active
  4. export async function initStopwatch() {
  5. if (!EnableTimetracking) {
  6. return;
  7. }
  8. const stopwatchEl = $('.active-stopwatch-trigger');
  9. if (!stopwatchEl.length) {
  10. return;
  11. }
  12. stopwatchEl.removeAttr('href'); // intended for noscript mode only
  13. stopwatchEl.popup({
  14. position: 'bottom right',
  15. hoverable: true,
  16. });
  17. // form handlers
  18. $('form > button', stopwatchEl).on('click', function () {
  19. $(this).parent().trigger('submit');
  20. });
  21. if (NotificationSettings.EventSourceUpdateTime > 0 && !!window.EventSource && window.SharedWorker) {
  22. // Try to connect to the event source via the shared worker first
  23. const worker = new SharedWorker(`${__webpack_public_path__}js/eventsource.sharedworker.js`, 'notification-worker');
  24. worker.addEventListener('error', (event) => {
  25. console.error(event);
  26. });
  27. worker.port.addEventListener('messageerror', () => {
  28. console.error('Unable to deserialize message');
  29. });
  30. worker.port.postMessage({
  31. type: 'start',
  32. url: `${window.location.origin}${AppSubUrl}/user/events`,
  33. });
  34. worker.port.addEventListener('message', (event) => {
  35. if (!event.data || !event.data.type) {
  36. console.error(event);
  37. return;
  38. }
  39. if (event.data.type === 'stopwatches') {
  40. updateStopwatchData(JSON.parse(event.data.data));
  41. } else if (event.data.type === 'error') {
  42. console.error(event.data);
  43. } else if (event.data.type === 'logout') {
  44. if (event.data.data !== 'here') {
  45. return;
  46. }
  47. worker.port.postMessage({
  48. type: 'close',
  49. });
  50. worker.port.close();
  51. window.location.href = AppSubUrl;
  52. } else if (event.data.type === 'close') {
  53. worker.port.postMessage({
  54. type: 'close',
  55. });
  56. worker.port.close();
  57. }
  58. });
  59. worker.port.addEventListener('error', (e) => {
  60. console.error(e);
  61. });
  62. worker.port.start();
  63. window.addEventListener('beforeunload', () => {
  64. worker.port.postMessage({
  65. type: 'close',
  66. });
  67. worker.port.close();
  68. });
  69. return;
  70. }
  71. if (NotificationSettings.MinTimeout <= 0) {
  72. return;
  73. }
  74. const fn = (timeout) => {
  75. setTimeout(async () => {
  76. await updateStopwatchWithCallback(fn, timeout);
  77. }, timeout);
  78. };
  79. fn(NotificationSettings.MinTimeout);
  80. const currSeconds = $('.stopwatch-time').data('seconds');
  81. if (currSeconds) {
  82. updateTimeInterval = updateStopwatchTime(currSeconds);
  83. }
  84. }
  85. async function updateStopwatchWithCallback(callback, timeout) {
  86. const isSet = await updateStopwatch();
  87. if (!isSet) {
  88. timeout = NotificationSettings.MinTimeout;
  89. } else if (timeout < NotificationSettings.MaxTimeout) {
  90. timeout += NotificationSettings.TimeoutStep;
  91. }
  92. callback(timeout);
  93. }
  94. async function updateStopwatch() {
  95. const data = await $.ajax({
  96. type: 'GET',
  97. url: `${AppSubUrl}/api/v1/user/stopwatches`,
  98. headers: {'X-Csrf-Token': csrf},
  99. });
  100. if (updateTimeInterval) {
  101. clearInterval(updateTimeInterval);
  102. updateTimeInterval = null;
  103. }
  104. return updateStopwatchData(data);
  105. }
  106. async function updateStopwatchData(data) {
  107. const watch = data[0];
  108. const btnEl = $('.active-stopwatch-trigger');
  109. if (!watch) {
  110. btnEl.addClass('hidden');
  111. } else {
  112. const {repo_owner_name, repo_name, issue_index, seconds} = watch;
  113. const issueUrl = `${AppSubUrl}/${repo_owner_name}/${repo_name}/issues/${issue_index}`;
  114. $('.stopwatch-link').attr('href', issueUrl);
  115. $('.stopwatch-commit').attr('action', `${issueUrl}/times/stopwatch/toggle`);
  116. $('.stopwatch-cancel').attr('action', `${issueUrl}/times/stopwatch/cancel`);
  117. $('.stopwatch-issue').text(`${repo_owner_name}/${repo_name}#${issue_index}`);
  118. $('.stopwatch-time').text(prettyMilliseconds(seconds * 1000));
  119. updateStopwatchTime(seconds);
  120. btnEl.removeClass('hidden');
  121. }
  122. return !!data.length;
  123. }
  124. async function updateStopwatchTime(seconds) {
  125. const secs = parseInt(seconds);
  126. if (!Number.isFinite(secs)) return;
  127. const start = Date.now();
  128. updateTimeInterval = setInterval(() => {
  129. const delta = Date.now() - start;
  130. const dur = prettyMilliseconds(secs * 1000 + delta, {compact: true});
  131. $('.stopwatch-time').text(dur);
  132. }, 1000);
  133. }