Add button to move widget bar to left side - tradingview.com

Allows user to move widgets like the chatbox to the left side of the app.

  1. // ==UserScript==
  2. // @name Add button to move widget bar to left side - tradingview.com
  3. // @namespace Itsnotlupus Industries
  4. // @match https://*.tradingview.com/*
  5. // @grant none
  6. // @version 1.2
  7. // @author itsnotlupus
  8. // @license MIT
  9. // @description Allows user to move widgets like the chatbox to the left side of the app.
  10. // @require https://greatest.deepsurf.us/scripts/468394-itsnotlupus-tiny-utilities/code/utils.js
  11. // @require https://greatest.deepsurf.us/scripts/471000-itsnotlupus-i18n-support/code/i18n.js
  12. // @grant GM_xmlhttpRequest
  13. // @grant GM_setValue
  14. // @grant GM_getValue
  15. // ==/UserScript==
  16.  
  17. /* jshint esversion:11 */
  18.  
  19. const LEFT_TOOLBAR_WIDTH = 52;
  20. const RIGHT_WIDGETBAR_WIDTH = 46;
  21.  
  22. (async function main() {
  23.  
  24. const strings = {
  25. scriptFailure: 'UserScript "Add button to move widget bar to left side" has become incompatible with TradingView and can no longer work.\nPlease disable or update this script.',
  26. buttonLabel: "Move Widgets to Other Side of Chart",
  27. nagLog: "We found an UNCLOSABLE modal dialog. My favorite!"
  28. };
  29.  
  30. i18n.setEnStrings([], strings);
  31. await i18n.initLanguage();
  32.  
  33. // If we can't initialize within 60 seconds, let users know the script isn't able to work as intended.
  34. const failTimer = setTimeout(() => {
  35. const msg = t`scriptFailure`;
  36. console.log(`%c${msg}`, 'font-weight:600;font-size:2em;color:red');
  37. throw new Error(msg);
  38. }, 60 * 1000);
  39.  
  40. // wait for app to load and store refs to relevant elements
  41. const [
  42. layoutAreaCenter,
  43. layoutAreaTradingPanel,
  44. layoutAreaLeft,
  45. layoutAreaRight,
  46. layoutAreaBottom,
  47. chartContainer,
  48. widgetbarPages,
  49. widgetbarWrap,
  50. widgetbarTabs,
  51. drawingToolbar,
  52. widgetToolbarFiller
  53. ] = await untilDOM(()=>{
  54. const elts = [
  55. $`.layout__area--center`,
  56. $`.layout__area--tradingpanel`,
  57. $`.layout__area--left`,
  58. $`.layout__area--right`,
  59. $`.layout__area--bottom`,
  60. $`.chart-container`,
  61. $`.widgetbar-pages`,
  62. $`.widgetbar-wrap`,
  63. $`.widgetbar-tabs`,
  64. $`#drawing-toolbar`,
  65. $`.widgetbar-tabs div[class^='filler-']`
  66. ];
  67. return elts.some(e=>!e) ? null : elts;
  68. });
  69.  
  70. // If we're here, we're probably fine.
  71. clearTimeout(failTimer);
  72.  
  73. // persistent state
  74. let widgetsMovedLeft = localStorage.widgetsMovedLeft === 'true';
  75. let lastAd = 0;
  76.  
  77. // augment UI
  78. // clone a button and customize it to make our "move widgets" button.
  79. const button = widgetToolbarFiller.nextElementSibling.cloneNode(true);
  80. button.dataset.name = "move_widgets";
  81. button.dataset.tooltip = button.ariaLabel = t`buttonLabel`;
  82. button.querySelector('svg').remove();
  83. button.querySelector('span').append(svg('svg', { width: 44, height: 44, viewBox: "0 0 21 21" }, // random SVG icon goes here. a UX person I am not. https://www.svgrepo.com/svg/343314/toggles
  84. svg('g', { fill:"none", "fill-rule":"evenodd", stroke:"currentColor", "stroke-width":"0.4", "stroke-linecap":"round", "stroke-linejoin":"round", transform:"translate(3 4)" },
  85. svg('circle', { cx:"3.5", cy:"3.5", r:"3"}),
  86. svg('path', {d:"M6 1.5h6.5c.8 0 2 .3 2 2s-1.2 2-2 2H6m5.5 8a3 3 0 1 1 0-6 3 3 0 0 1 0 6z"}),
  87. svg('path', {d:"M9 8.5H2.5c-.8 0-2 .3-2 2s1.2 2 2 2H9"})
  88. )
  89. ));
  90. button.addEventListener('click', () => toggleWidgets());
  91. widgetToolbarFiller.after(button);
  92.  
  93. // apply state to app.
  94. toggleWidgets(widgetsMovedLeft);
  95.  
  96. // start observing DOM to adjust layout continuously.
  97. observeDOM(adjustLayout);
  98.  
  99. function toggleWidgets(left = !widgetsMovedLeft) {
  100. const parent = left ? layoutAreaLeft : layoutAreaRight;
  101. parent.prepend(widgetbarWrap);
  102. widgetbarTabs.style.right = left ? '' : '0';
  103. widgetbarTabs.style.left = left ? '0' : '';
  104. widgetbarPages.style.right = left ? '51px' : '';
  105.  
  106. widgetsMovedLeft = left;
  107. localStorage.widgetsMovedLeft = left;
  108. adjustLayout();
  109. }
  110.  
  111. function adjustLayout() {
  112. const widgetWidth = RIGHT_WIDGETBAR_WIDTH + widgetbarPages.clientWidth;
  113. const rightWidth = widgetsMovedLeft ? 0 : widgetWidth;
  114. const leftWidth = LEFT_TOOLBAR_WIDTH + (widgetsMovedLeft ? widgetWidth : 0);
  115. const centerWidth = innerWidth - leftWidth - rightWidth - 8;
  116. const centerLeft = leftWidth + 4;
  117.  
  118. const set = (elt, props) => Object.keys(props).forEach(p => elt.style[p] = props[p] + 'px');
  119.  
  120. set(drawingToolbar, {
  121. width: LEFT_TOOLBAR_WIDTH,
  122. marginLeft: leftWidth - LEFT_TOOLBAR_WIDTH + 2
  123. });
  124.  
  125. set(layoutAreaRight, { width: rightWidth });
  126. set(layoutAreaLeft, { width: leftWidth });
  127.  
  128. set(layoutAreaCenter, {
  129. width: centerWidth,
  130. left: centerLeft
  131. });
  132. set(chartContainer, { width: centerWidth });
  133. set(layoutAreaBottom, {
  134. width: centerWidth,
  135. left: centerLeft
  136. });
  137. set(layoutAreaTradingPanel, { right: rightWidth });
  138.  
  139. // remove some nags since we're already here.
  140. $`div[data-role='toast-container']`.querySelector('button')?.click();
  141. const gopro = $`[data-dialog-name='gopro']`;
  142. if (gopro) {
  143. // does it have a close button?
  144. const closeButton = gopro.querySelector('button[aria-label="close"],button[class^="close"]');
  145. if (closeButton) {
  146. closeButton.click();
  147. } else {
  148. // unclosable nag? Cool.
  149. console.error(t`nagLog`);
  150. gopro.parentElement.remove();
  151. }
  152. }
  153. }
  154. })();