<commit_before>import * as debounce from 'lodash/debounce'
import router from '../../router'
import Chessground from '../../chessground/Chessground'
import * as cg from '../../chessground/interfaces'
import * as chess from '../../chess'
import * as chessFormat from '../../utils/chessFormat'
import { build as makeTree, path as treePath, ops as treeOps, TreeWrapper, Tree } from '../shared/tree'
import redraw from '../../utils/redraw'
import session from '../../session'
import sound from '../../sound'
import socket from '../../socket'
import { openingSensibleVariants } from '../../lichess/variant'
import * as gameApi from '../../lichess/game'
import { AnalyseData, AnalyseDataWithTree } from '../../lichess/interfaces/analyse'
import { Opening } from '../../lichess/interfaces/game'
import settings from '../../settings'
import { oppositeColor, hasNetwork, noop } from '../../utils'
import promotion from '../shared/offlineRound/promotion'
import continuePopup, { Controller as ContinuePopupController } from '../shared/continuePopup'
import { NotesCtrl } from '../shared/round/notes'
import * as util from './util'
import cevalCtrl from './ceval/cevalCtrl'
import crazyValid from './crazy/crazyValid'
import ExplorerCtrl from './explorer/ExplorerCtrl'
import menu, { IMainMenuCtrl } from './menu'
import analyseSettings, { ISettingsCtrl } from './analyseSettings'
import ground from './ground'
import socketHandler from './analyseSocketHandler'
import { Source, IExplorerCtrl, CevalCtrlInterface, CevalEmit } from './interfaces'
import * as tabs from './tabs'

export default class AnalyseCtrl {
  data: AnalyseData
  orientation: Color
  source: Source

  settings: ISettingsCtrl
  menu: IMainMenuCtrl
  continuePopup: ContinuePopupController
  notes: NotesCtrl | null
  chessground: Chessground
  ceval: CevalCtrlInterface
  explorer: IExplorerCtrl
  tree: TreeWrapper

  // current tree state, cursor, and denormalized node lists
  path: Tree.Path
  node: Tree.Node
  nodeList: Tree.Node[]
  mainline: Tree.Node[]

  // state flags
  onMainline: boolean = true
  synthetic: boolean // false if coming from a real game
  ongoing: boolean // true if real game is ongoing

  // paths
  initialPath: Tree.Path
  gamePath?: Tree.Path

  // various view state flags
  replaying: boolean = false
  cgConfig?: cg.SetConfig
  shouldGoBack: boolean
  formattedDate: string

  private _currentTabIndex: number = 0

  private debouncedExplorerSetStep: () => void

  public static decomposeUci(uci: string): [Key, Key, SanChar] {
    return [<Key>uci.slice(0, 2), <Key>uci.slice(2, 4), <SanChar>uci.slice(4, 5)]
  }

  constructor(
    data: AnalyseData,
    source: Source,
    orientation: Color,
    shouldGoBack: boolean,
    ply?: number,
    tab?: number
  ) {
    this.data = data
    this.orientation = orientation
    this.source = source
    this.synthetic = util.isSynthetic(data)
    this.initialPath = treePath.root
    this._currentTabIndex = tab !== undefined ? tab :
      this.synthetic ? 0 : 1

    if (settings.analyse.supportedVariants.indexOf(this.data.game.variant.key) === -1) {
      window.plugins.toast.show(`Analysis board does not support ${this.data.game.variant.name} variant.`, 'short', 'center')
      router.set('/')
    }

    this.tree = makeTree(treeOps.reconstruct(this.data.treeParts))

    this.settings = analyseSettings.controller(this)
    this.menu = menu.controller(this)
    this.continuePopup = continuePopup.controller()

    // TODO
    // this.notes = session.isConnected() && this.data.game.speed === 'correspondence' ? new NotesCtrl(this.data) : null
    this.notes = null

    this.ceval = cevalCtrl(this.data.game.variant.key, this.allowCeval(), this.onCevalMsg)
    this.explorer = ExplorerCtrl(this)
    this.debouncedExplorerSetStep = debounce(this.explorer.setStep, this.data.pref.animationDuration + 50)

    const initPly = Number(ply) ||
      (location.hash && parseInt(location.hash.replace(/#/, ''), 10)) ||
      (this.source === 'online' && gameApi.isPlayerPlaying(this.data) ?
        this.tree.lastPly() : this.tree.firstPly())

    this.gamePath = (this.synthetic || this.ongoing) ? undefined :
      treePath.fromNodeList(treeOps.mainlineNodeList(this.tree.root))

    const mainline = treeOps.mainlineNodeList(this.tree.root)
    this.initialPath = treeOps.takePathWhile(mainline, n => n.ply <= initPly)
    this.setPath(this.initialPath)

    const gameMoment = window.moment(this.data.game.createdAt)

    this.shouldGoBack = shouldGoBack
    this.formattedDate = gameMoment.format('L LT')

    if (this.isRemoteAnalysable()) {
      this.connectGameSocket()
    } else {
      socket.createDefault()
    }

    this.showGround()

    if (this.currentTab(this.availableTabs()).id === 'explorer') {
      this.debouncedExplorerSetStep()
    }

    setTimeout(this.debouncedScroll, 250)
    setTimeout(this.initCeval, 1000)
    window.plugins.insomnia.keepAwake()
  }

  canDrop = () => {
    return true
  }

  player = () => {
    return this.data.game.player
  }

  connectGameSocket = () => {
    if (hasNetwork() &&
      this.data.url !== undefined &&
      this.data.player.version !== undefined
    ) {
      socket.createGame(
        this.data.url.socket,
        this.data.player.version,
        socketHandler(this),
        this.data.url.round
      )
    }
  }

  setPath = (path: Tree.Path): void => {
    this.path = path
    this.nodeList = this.tree.getNodeList(path)
    this.node = treeOps.last(this.nodeList) as Tree.Node
    this.mainline = treeOps.mainlineNodeList(this.tree.root)
    this.onMainline = this.tree.pathIsMainline(path)
  }

  availableTabs = (): tabs.Tab[] => {
    let val = tabs.defaults

    if (this.synthetic) val = val.filter(t => t.id !== 'infos')
    if (hasNetwork()) val = val.concat([tabs.explorer])
    if (gameApi.analysable(this.data)) val = val.concat([tabs.charts])

    return val
  }

  currentTabIndex = (avail: tabs.Tab[]): number => {
    if (this._currentTabIndex > avail.length - 1) return avail.length - 1
    else return this._currentTabIndex
  }

  currentTab = (avail: tabs.Tab[]): tabs.Tab => {
    return avail[this.currentTabIndex(avail)]
  }

  onTabChange = (index: number) => {
    const loc = window.location.search.replace(/\?tab\=\w+$/, '')
    try {
      window.history.replaceState(window.history.state, '', loc + '?tab=' + index)
    } catch (e) {
      console.error(e)
    }
    this._currentTabIndex = index
    this.explorer.setStep()
    redraw()
  }

  initCeval = () => {
    if (this.ceval.enabled()) {
      if (this.ceval.isInit()) {
        this.startCeval()
      } else {
        this.ceval.init().then(this.startCeval)
      }
    }
  }

  debouncedScroll = debounce(() => util.autoScroll(document.getElementById('replay')), 200)

  jump = (path: Tree.Path, direction?: 'forward' | 'backward') => {
    this.setPath(path)
    // this.toggleVariationMenu()
    this.showGround()
    this.fetchOpening()
    if (this.node && this.node.san && direction === 'forward') {
      if (this.node.san.indexOf('x') !== -1) sound.throttledCapture()
      else sound.throttledMove()
    }
    this.ceval.stop()
    this.debouncedExplorerSetStep()
    this.updateHref()
    this.startCeval()<commit_msg>Scroll on tab change when on move list<commit_after>import * as debounce from 'lodash/debounce'
import router from '../../router'
import Chessground from '../../chessground/Chessground'
import * as cg from '../../chessground/interfaces'
import * as chess from '../../chess'
import * as chessFormat from '../../utils/chessFormat'
import { build as makeTree, path as treePath, ops as treeOps, TreeWrapper, Tree } from '../shared/tree'
import redraw from '../../utils/redraw'
import session from '../../session'
import sound from '../../sound'
import socket from '../../socket'
import { openingSensibleVariants } from '../../lichess/variant'
import * as gameApi from '../../lichess/game'
import { AnalyseData, AnalyseDataWithTree } from '../../lichess/interfaces/analyse'
import { Opening } from '../../lichess/interfaces/game'
import settings from '../../settings'
import { oppositeColor, hasNetwork, noop } from '../../utils'
import promotion from '../shared/offlineRound/promotion'
import continuePopup, { Controller as ContinuePopupController } from '../shared/continuePopup'
import { NotesCtrl } from '../shared/round/notes'
import * as util from './util'
import cevalCtrl from './ceval/cevalCtrl'
import crazyValid from './crazy/crazyValid'
import ExplorerCtrl from './explorer/ExplorerCtrl'
import menu, { IMainMenuCtrl } from './menu'
import analyseSettings, { ISettingsCtrl } from './analyseSettings'
import ground from './ground'
import socketHandler from './analyseSocketHandler'
import { Source, IExplorerCtrl, CevalCtrlInterface, CevalEmit } from './interfaces'
import * as tabs from './tabs'

export default class AnalyseCtrl {
  data: AnalyseData
  orientation: Color
  source: Source

  settings: ISettingsCtrl
  menu: IMainMenuCtrl
  continuePopup: ContinuePopupController
  notes: NotesCtrl | null
  chessground: Chessground
  ceval: CevalCtrlInterface
  explorer: IExplorerCtrl
  tree: TreeWrapper

  // current tree state, cursor, and denormalized node lists
  path: Tree.Path
  node: Tree.Node
  nodeList: Tree.Node[]
  mainline: Tree.Node[]

  // state flags
  onMainline: boolean = true
  synthetic: boolean // false if coming from a real game
  ongoing: boolean // true if real game is ongoing

  // paths
  initialPath: Tree.Path
  gamePath?: Tree.Path

  // various view state flags
  replaying: boolean = false
  cgConfig?: cg.SetConfig
  shouldGoBack: boolean
  formattedDate: string

  private _currentTabIndex: number = 0

  private debouncedExplorerSetStep: () => void

  public static decomposeUci(uci: string): [Key, Key, SanChar] {
    return [<Key>uci.slice(0, 2), <Key>uci.slice(2, 4), <SanChar>uci.slice(4, 5)]
  }

  constructor(
    data: AnalyseData,
    source: Source,
    orientation: Color,
    shouldGoBack: boolean,
    ply?: number,
    tab?: number
  ) {
    this.data = data
    this.orientation = orientation
    this.source = source
    this.synthetic = util.isSynthetic(data)
    this.initialPath = treePath.root
    this._currentTabIndex = tab !== undefined ? tab :
      this.synthetic ? 0 : 1

    if (settings.analyse.supportedVariants.indexOf(this.data.game.variant.key) === -1) {
      window.plugins.toast.show(`Analysis board does not support ${this.data.game.variant.name} variant.`, 'short', 'center')
      router.set('/')
    }

    this.tree = makeTree(treeOps.reconstruct(this.data.treeParts))

    this.settings = analyseSettings.controller(this)
    this.menu = menu.controller(this)
    this.continuePopup = continuePopup.controller()

    // TODO
    // this.notes = session.isConnected() && this.data.game.speed === 'correspondence' ? new NotesCtrl(this.data) : null
    this.notes = null

    this.ceval = cevalCtrl(this.data.game.variant.key, this.allowCeval(), this.onCevalMsg)
    this.explorer = ExplorerCtrl(this)
    this.debouncedExplorerSetStep = debounce(this.explorer.setStep, this.data.pref.animationDuration + 50)

    const initPly = Number(ply) ||
      (location.hash && parseInt(location.hash.replace(/#/, ''), 10)) ||
      (this.source === 'online' && gameApi.isPlayerPlaying(this.data) ?
        this.tree.lastPly() : this.tree.firstPly())

    this.gamePath = (this.synthetic || this.ongoing) ? undefined :
      treePath.fromNodeList(treeOps.mainlineNodeList(this.tree.root))

    const mainline = treeOps.mainlineNodeList(this.tree.root)
    this.initialPath = treeOps.takePathWhile(mainline, n => n.ply <= initPly)
    this.setPath(this.initialPath)

    const gameMoment = window.moment(this.data.game.createdAt)

    this.shouldGoBack = shouldGoBack
    this.formattedDate = gameMoment.format('L LT')

    if (this.isRemoteAnalysable()) {
      this.connectGameSocket()
    } else {
      socket.createDefault()
    }

    this.showGround()

    if (this.currentTab(this.availableTabs()).id === 'explorer') {
      this.debouncedExplorerSetStep()
    }

    setTimeout(this.debouncedScroll, 250)
    setTimeout(this.initCeval, 1000)
    window.plugins.insomnia.keepAwake()
  }

  canDrop = () => {
    return true
  }

  player = () => {
    return this.data.game.player
  }

  connectGameSocket = () => {
    if (hasNetwork() &&
      this.data.url !== undefined &&
      this.data.player.version !== undefined
    ) {
      socket.createGame(
        this.data.url.socket,
        this.data.player.version,
        socketHandler(this),
        this.data.url.round
      )
    }
  }

  setPath = (path: Tree.Path): void => {
    this.path = path
    this.nodeList = this.tree.getNodeList(path)
    this.node = treeOps.last(this.nodeList) as Tree.Node
    this.mainline = treeOps.mainlineNodeList(this.tree.root)
    this.onMainline = this.tree.pathIsMainline(path)
  }

  availableTabs = (): tabs.Tab[] => {
    let val = tabs.defaults

    if (this.synthetic) val = val.filter(t => t.id !== 'infos')
    if (hasNetwork()) val = val.concat([tabs.explorer])
    if (gameApi.analysable(this.data)) val = val.concat([tabs.charts])

    return val
  }

  currentTabIndex = (avail: tabs.Tab[]): number => {
    if (this._currentTabIndex > avail.length - 1) return avail.length - 1
    else return this._currentTabIndex
  }

  currentTab = (avail: tabs.Tab[]): tabs.Tab => {
    return avail[this.currentTabIndex(avail)]
  }

  onTabChange = (index: number) => {
    const loc = window.location.search.replace(/\?tab\=\w+$/, '')
    try {
      window.history.replaceState(window.history.state, '', loc + '?tab=' + index)
    } catch (e) {
      console.error(e)
    }
    this._currentTabIndex = index
    const cur = this.currentTab(this.availableTabs())
    if (cur.id === 'moves') this.debouncedScroll()
    this.explorer.setStep()
    redraw()
  }

  initCeval = () => {
    if (this.ceval.enabled()) {
      if (this.ceval.isInit()) {
        this.startCeval()
      } else {
        this.ceval.init().then(this.startCeval)
      }
    }
  }

  debouncedScroll = debounce(() => util.autoScroll(document.getElementById('replay')), 200)

  jump = (path: Tree.Path, direction?: 'forward' | 'backward') => {
    this.setPath(path)
    // this.toggleVariationMenu()
    this.showGround()
    this.fetchOpening()
    if (this.node && this.node.san && direction === 'forward') {
      if (this.node.san.indexOf('x') !== -1) sound.throttledCapture()
      else sound.throttledMove()
    }
    this.ceval.stop()
    this.debouncedExplorerSetStep()
    this.updateHref()
    this.startCeval()
{'fraction_non_alphanumeric': 0.14305251641137856, 'fraction_numerical': 0.003692560175054705, 'mean_word_length': 3.2428198433420365, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 39, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3724288', 'n_tokens_mistral': 4729, 'n_tokens_neox': 4577, 'n_words': 1015}