text: 
  import IndexedArray from 'arrayslicer'
  import Const from './constants.js'
  
  export default {
  
      clamp(num, min, max) {
          return num <= min ? min : num >= max ? max : num
      },
  
      add_zero(i) {
          if (i < 10) {
              i = "0" + i;
          }
          return i
      },
  
      // Start of the day (zero millisecond)
      day_start(t) {
          let start = new Date(t)
          return start.setUTCHours(0,0,0,0)
      },
  
      // Start of the month
      month_start(t) {
          let date = new Date(t)
          return Date.UTC(
              date.getFullYear(),
              date.getMonth(), 1
          )
      },
  
      // Start of the year
      year_start(t) {
          return Date.UTC(new Date(t).getFullYear())
      },
  
      get_year(t) {
          if (!t) return undefined
          return new Date(t).getUTCFullYear()
      },
  
      get_month(t) {
          if (!t) return undefined
          return new Date(t).getUTCMonth()
      },
  
      // Nearest in array
      nearest_a(x, array) {
          let dist = Infinity
          let val = null
          let index = -1
          for (var i = 0; i < array.length; i++) {
              var xi = array[i]
              if (Math.abs(xi - x) < dist) {
                  dist = Math.abs(xi - x)
                  val = xi
                  index = i
              }
          }
          return [index, val]
      },
  
      round(num, decimals = 8) {
          return parseFloat(num.toFixed(decimals))
      },
  
      // Strip? No, it's ugly floats in js
      strip(number) {
          return parseFloat(
              parseFloat(number).toPrecision(12)
          )
      },
  
      get_day(t) {
          return t ? new Date(t).getDate() : null
      },
  
      // Update array keeping the same reference
      overwrite(arr, new_arr) {
          arr.splice(0, arr.length, ...new_arr)
      },
  
      // Copy layout in reactive way
      copy_layout(obj, new_obj) {
          for (var k in obj) {
              if (Array.isArray(obj[k])) {
                  // (some offchart indicators are added/removed)
                  // we need to update layout in a reactive way
                  if (obj[k].length !== new_obj[k].length) {
                      this.overwrite(obj[k], new_obj[k])
                      continue
                  }
                  for (var m in obj[k]) {
                      Object.assign(obj[k][m], new_obj[k][m])
                  }
              } else {
                  Object.assign(obj[k], new_obj[k])
              }
          }
      },
  
      // Detects candles interval
      detect_interval(ohlcv) {
          let len = Math.min(ohlcv.length - 1, 99)
          let min = Infinity
          ohlcv.slice(0, len).forEach((x, i) => {
              let d = ohlcv[i+1][0] - x[0]
              if (d === d && d < min) min = d
          })
          // This saves monthly chart from being awkward
          if (min >= Const.MONTH && min <= Const.DAY * 30) {
              return Const.DAY * 31
          }
          return min
      },
  
      // Gets numberic part of overlay id (e.g 'EMA_1' = > 1)
      get_num_id(id) {
          return parseInt(id.split('_').pop())
      },
  
      // Fast filter. Really fast, like 10X
      fast_filter(arr, t1, t2) {
          if (!arr.length) return arr
          try {
              let ia = new IndexedArray(arr, "0")
              let res = ia.getRange(t1, t2)
              let i0 = ia.valpos[t1].next
              return [res, i0]
          } catch(e) {
              // Something wrong with fancy slice lib
              // Fast fix: fallback to filter
              return [arr.filter(x =>
                  x[0] >= t1 && x[0] <= t2
              ), 0]
          }
      },
  
      // Fast filter (index-based)
      fast_filter_i(arr, t1, t2) {
          if (!arr.length) return arr
          let i1 =  Math.floor(t1)
          if (i1 < 0) i1 = 0
          let i2 =  Math.floor(t2 + 1)
          let res = arr.slice(i1, i2)
          return [res, i1]
      },
  
      // Nearest indexes (left and right)
      fast_nearest(arr, t1) {
          let ia = new IndexedArray(arr, "0")
          ia.fetch(t1)
          return [ia.nextlow, ia.nexthigh]
      },
  
      now() { return (new Date()).getTime() },
  
      pause(delay) {
          return new Promise((rs, rj) => setTimeout(rs, delay))
      },
  
      // Limit crazy wheel delta values
      smart_wheel(delta) {
          let abs = Math.abs(delta)
          if (abs > 500) {
              return (200 + Math.log(abs)) * Math.sign(delta)
          }
          return delta
      },
  
      // Parse the original mouse event to find deltaX
      get_deltaX(event) {
          return event.originalEvent.deltaX / 12
      },
  
      // Parse the original mouse event to find deltaY
      get_deltaY(event) {
          return event.originalEvent.deltaY / 12
      },
  
      // Apply opacity to a hex color
      apply_opacity(c, op) {
          if (c.length === 7) {
              let n = Math.floor(op * 255)
              n = this.clamp(n, 0, 255)
              c += n.toString(16)
          }
          return c
      },
  
      // Parse timeframe or return value in ms
      parse_tf(smth) {
          if (typeof smth === 'string') {
              return Const.map_unit[smth]
          } else {
              return smth
          }
      },
  
      // Detect index shift between the main data sub
      // and the overlay's sub (for IB-mode)
      index_shift(sub, data) {
  
          // Find the second timestamp (by value)
          if (!data.length) return 0
          let first = data[0][0]
          let second
  
          for (var i = 1; i < data.length; i++) {
              if (data[i][0] !== first) {
                  second = data[i][0]
                  break
              }
          }
  
          for (var j = 0; j < sub.length; j++) {
              if (sub[j][0] === second) {
                  return j - i
              }
          }
  
          return 0
      },
  
      // Fallback fix for Brave browser
      // https://github.com/brave/brave-browser/issues/1738
      measureText(ctx, text, tv_id) {
          let m = ctx.measureTextOrg(text)
          if (m.width === 0) {
              const doc = document
              const id = 'tvjs-measure-text'
              let el = doc.getElementById(id)
              if (!el) {
                  let base = doc.getElementById(tv_id)
                  el = doc.createElement("div")
                  el.id = id
                  el.style.position = 'absolute'
                  el.style.top = '-1000px'
                  base.appendChild(el)
              }
              if(ctx.font) el.style.font = ctx.font
              el.innerText = text.replace(/ /g, '.');
              return { width: el.offsetWidth }
          } else {
              return m
          }
      },
  
      uuid(temp = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx') {
          return temp
              .replace(/[xy]/g, c => {
              var r = Math.random() * 16 | 0, v = c == 'x' ?
                  r :
                  (r & 0x3 | 0x8)
              return v.toString(16)
          })
      },
  
      uuid2() {
          return this.uuid('xxxxxxxxxxxx')
      },
  
      // Delayed warning, f = condition lambda fn
      warn(f, text, delay = 0) {
          setTimeout(() => {
              if (f()) console.warn(text)
          }, delay)
      },
  
      // Checks if script props updated
      // (and not style settings or something else)
      is_scr_props_upd(n, prev) {
          let p = prev.find(x => x.v.$uuid === n.v.$uuid)
          if (!p) return false
  
          let props = n.p.settings.$props
          if (!props) return false
  
          return props.some(x => n.v[x] !== p.v[x])
      },
  
      // Checks if it's time to make a script update
      // (based on execInterval in ms)
      delayed_exec(v) {
          if (!v.script || !v.script.execInterval)
              return true
          let t = this.now()
          let dt = v.script.execInterval
          if (!v.settings.$last_exec ||
              t > v.settings.$last_exec + dt) {
              v.settings.$last_exec = t
              return true
          }
          return false
      },
  
      // Format names such 'RSI, $length', where
      // length - is one of the settings
      format_name(ov) {
          if (!ov.name) return undefined
  
          let name = ov.name
  
          for (var k in ov.settings || {}) {
              let val = ov.settings[k]
              let reg = new RegExp(`\\$${k}`, 'g')
              name = name.replace(reg, val)
          }
  
          return name
      }
  
  }
  