CircleCI Workflow Panner

Pan across large workflow charts using your mouse instead of relying on the scrollbars. Set naturalScroll to false to disable inverted panning.

  1. // ==UserScript==
  2. // @name CircleCI Workflow Panner
  3. // @namespace antontsvil
  4. // @match *://app.circleci.com/*
  5. // @grant GM.getValue
  6. // @grant GM.setValue
  7. // @require https://unpkg.com/arrive@2.4.1/src/arrive.js
  8. // @version 1.1
  9. // @license MIT
  10. // @author antontsvil@gmail.com
  11. // @description Pan across large workflow charts using your mouse instead of relying on the scrollbars. Set naturalScroll to false to disable inverted panning.
  12. // ==/UserScript==
  13. /*jshint esversion: 8 */
  14.  
  15. const graphSelector = "[data-cy='workflow-graph']";
  16. const graph = () => document.querySelector(graphSelector);
  17. const html = () => document.querySelector('html');
  18.  
  19. let isDragging = false;
  20. const dragStart = () => {
  21. isDragging = true;
  22. const gel = graph();
  23. gel.style.cursor = 'move';
  24. gel.style.userSelect = 'none';
  25. };
  26. const dragStop = () => {
  27. isDragging = false;
  28. const gel = graph();
  29. gel.style.cursor = 'default';
  30. gel.style.userSelect = 'initial';
  31. };
  32.  
  33. const b = document.querySelector('body');
  34. b.arrive(graphSelector, async () => {
  35. let naturalScroll = await GM.getValue('naturalScroll');
  36. if (naturalScroll!== false && naturalScroll !== true) {
  37. await GM.setValue('naturalScroll', true);
  38. naturalScroll = true;
  39. }
  40. const gel = graph();
  41. gel.addEventListener('mousedown', ({ target, button }) => {
  42. if (target.tagName === 'svg' && button === 0) {
  43. dragStart();
  44. }
  45. });
  46. gel.addEventListener('mouseup', (event) => {
  47. dragStop();
  48. });
  49. gel.addEventListener('mouseout', (event) => {
  50. if (event.relatedTarget === gel) {
  51. dragStop();
  52. }
  53. });
  54. gel.addEventListener('mousemove', (event) => {
  55. const el = graph();
  56. const rightSide = el.scrollWidth - el.clientWidth;
  57. const horizontalScroll = {
  58. left: naturalScroll
  59. ? el.scrollLeft - event.movementX
  60. : el.scrollLeft + event.movementX,
  61. };
  62. const verticalScroll = {
  63. top: naturalScroll
  64. ? html().scrollTop - event.movementY
  65. : html().scrollTop + event.movementY,
  66. };
  67. if (isDragging) {
  68. el.scrollTo(horizontalScroll);
  69. html().scrollTo(verticalScroll);
  70. }
  71. });
  72. });