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.

当前为 2023-04-11 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        Add button to move widget bar to left side - tradingview.com
// @namespace   Itsnotlupus Industries
// @match       https://www.tradingview.com/*
// @grant       none
// @version     1.0
// @author      itsnotlupus
// @license     MIT
// @description Allows user to move widgets like the chatbox to the left side of the app.
// ==/UserScript==

/* jshint esversion:11 */

// GENERIC UTILITIES

/** calls a function whenever the DOM changes */
const observeDOM = (fn, e = document.documentElement, config = { attributes: 1, childList: 1, subtree: 1 }) => {
  const observer = new MutationObserver(fn);
  observer.observe(e, config);
  return () => observer.disconnect();
};
/** check a condition on every DOM change until truthy. async returns truthy value */
const untilDOM = f => new Promise((r,_,d = observeDOM(() => (_=f()) && d() | r(_) )) => 0);

const crel = (name, attrs, ...children) => ((e = Object.assign(document.createElement(name), attrs)) => (e.append(...children), e))();
const svg = (name, attrs, ...children) =>  {
  const e = document.createElementNS('http://www.w3.org/2000/svg', name);
  Object.entries(attrs).forEach(([key,val]) => e.setAttribute(key, val));
  e.append(...children);
  return e;
}

const $ = s => document.querySelector(s);

// SCRIPT LOGIC BEGINS HERE.

const LEFT_TOOLBAR_WIDTH = 52;
const RIGHT_WIDGETBAR_WIDTH = 46;

run();

async function run() {
  // wait for stuff to load
  await untilDOM(()=>$`.widgetbar-wrap`);

  // store refs to relevant elements
  const layoutAreaCenter = $`.layout__area--center`;
  const layoutAreaTradingPanel = $`.layout__area--tradingpanel`;
  const layoutAreaLeft = $`.layout__area--left`;
  const layoutAreaRight = $`.layout__area--right`;
  const layoutAreaBottom = $`.layout__area--bottom`;
  const chartContainer = $`.chart-container`;
  const widgetbarPages = $`.widgetbar-pages`;
  const widgetbarWrap = $`.widgetbar-wrap`;
  const widgetbarTabs = $`.widgetbar-tabs`;
  const drawingToolbar = $`#drawing-toolbar`;
  const widgetToolbarFiller = $`.widgetbar-tabs div[class^='filler-']`;

  // This script is inherently tightly coupled to a number of small implementation details on tradingview.com
  // Whenever the TradingView app is updated, this script may stop working properly.
  // Here, we attempt to detect fundamental changes and bail out rather than breaking the app.
  // This is a "best effort" check, and this script might still break future versions of the app.
  // If that happens, disabling the script and reloading the app should fix things.
  if (!layoutAreaCenter || !layoutAreaTradingPanel || !layoutAreaLeft || !layoutAreaRight || !layoutAreaBottom || !chartContainer || !widgetbarPages || !widgetbarWrap || !widgetbarTabs || !drawingToolbar || !widgetToolbarFiller) {
    const msg = '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.';
    console.log(`%c${msg}`, 'font-weight:600;font-size:2em;color:red');
    throw new Error(msg);
  }

  // persistent state
  let widgetsMovedLeft = localStorage.widgetsMovedLeft === 'true';

  // augment UI
  // clone a button and customize it to make our "move widgets" button.
  const button = widgetToolbarFiller.nextElementSibling.cloneNode(true);
  button.dataset.name = "move_widgets";
  button.title = "Move Widgets to Other Side of Chart"; // l10n schm10n.
  button.querySelector('svg').remove();
  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
                                          svg('g', { fill:"none", "fill-rule":"evenodd", stroke:"currentColor", "stroke-width":"0.4", "stroke-linecap":"round", "stroke-linejoin":"round", transform:"translate(3 4)" },
                                              svg('circle', { cx:"3.5", cy:"3.5", r:"3"}),
                                              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"}),
                                              svg('path', {d:"M9 8.5H2.5c-.8 0-2 .3-2 2s1.2 2 2 2H9"})
                                          )
                                     ));
  button.addEventListener('click', () => toggleWidgets());
  widgetToolbarFiller.after(button);

  // apply state to app.
  toggleWidgets(widgetsMovedLeft);

  // start observing DOM to adjust layout continuously.
  observeDOM(adjustLayout);

  function toggleWidgets(left = !widgetsMovedLeft) {
    const parent = left ? layoutAreaLeft : layoutAreaRight;
    parent.prepend(widgetbarWrap);
    widgetbarTabs.style.right = left ? '' : '0';
    widgetbarTabs.style.left = left ? '0' : '';
    widgetbarPages.style.right = left ? '51px' : '';

    widgetsMovedLeft = left;
    localStorage.widgetsMovedLeft = left;
    adjustLayout();
  }

  function adjustLayout() {
    const rightWidth = widgetsMovedLeft ? 0 : RIGHT_WIDGETBAR_WIDTH;
    const leftWidth = LEFT_TOOLBAR_WIDTH + (widgetsMovedLeft ? RIGHT_WIDGETBAR_WIDTH + parseInt(widgetbarPages.style.width) : 0);
    const centerWidth = innerWidth - leftWidth - rightWidth - 8;
    const centerLeft = leftWidth + 4

    set(drawingToolbar, 'width', LEFT_TOOLBAR_WIDTH);
    set(drawingToolbar, 'marginLeft', leftWidth - LEFT_TOOLBAR_WIDTH + 2);

    set(layoutAreaRight, 'width', rightWidth);
    set(layoutAreaLeft, 'width', leftWidth);

    set(layoutAreaCenter, 'width', centerWidth);
    set(layoutAreaCenter, 'left', centerLeft);
    set(chartContainer, 'width', centerWidth);
    set(layoutAreaBottom, 'width', centerWidth);
    set(layoutAreaBottom, 'left', centerLeft);
    set(layoutAreaTradingPanel, 'right', rightWidth);

    // remove some nags since we're already here.
    document.querySelector("div[data-role='toast-container']").querySelector('button')?.click();
    document.querySelector("[data-dialog-name='gopro']")?.querySelector('button')?.click();

    function set(elt, prop, val) {
      // a gentle style setter that doesn't trigger unnecessary DOM mutations.
      if (elt.style[prop] !== val +'px') {
        elt.style[prop] = val + 'px';
      }
    }
  }
}