<commit_before>import * as windowConfigs from 'browser/windows/controller'
import { WindowConfig } from 'browser/types'
import { BrowserWindow, BrowserWindowConstructorOptions } from 'electron'
import { existsSync, readFileSync } from 'fs'
import kebabCase from 'lodash/fp/kebabCase'
import { Session } from 'main/types'
import { join } from 'path'

const playbackWindowName = 'playback-window'
const playbackCSS = readFileSync(join(__dirname, 'highlight.css'), 'utf-8')
const playbackWindowOptions = {
  webPreferences: {
    nodeIntegration: false,
    nodeIntegrationInSubFrames: true,
    preload: join(__dirname, `playback-window-preload-bundle.js`),
  },
}

const projectEditorWindowName = 'project-editor'

export type WindowLoader = (
  opts?: BrowserWindowConstructorOptions
) => BrowserWindow
export interface WindowLoaderMap {
  [key: string]: WindowLoader
}
export type WindowLoaderFactory = (session: Session) => WindowLoader
export interface WindowLoaderFactoryMap {
  [key: string]: WindowLoaderFactory
}
const windowLoaderFactoryMap: WindowLoaderFactoryMap = Object.fromEntries(
  Object.entries(windowConfigs).map(
    ([key, { window }]: [string, WindowConfig]) => {
      const filename = kebabCase(key)
      const preloadPath = join(__dirname, `${filename}-preload-bundle.js`)
      const hasPreload = existsSync(preloadPath)
      const windowLoader: WindowLoaderFactory =
        (session: Session) =>
        (options: BrowserWindowConstructorOptions = {}) => {
          const windowConfig = window(session)
          const win = new BrowserWindow({
            ...windowConfig,
            webPreferences: {
              ...windowConfig?.webPreferences ?? {}, 
              preload: hasPreload ? preloadPath : undefined,
            },
            ...options,
          })
          win.loadFile(join(__dirname, `${filename}.html`))
          return win
        }
      return [key, windowLoader]
    }
  )
)

const makeWindowLoaders = (session: Session): WindowLoaderMap =>
  Object.fromEntries(
    Object.entries(windowLoaderFactoryMap).map(([key, loader]) => [
      kebabCase(key),
      loader(session),
    ])
  )

export default class WindowsController {
  constructor(session: Session) {
    this.session = session
    this.playbackWindows = []
    this.windowLoaders = makeWindowLoaders(session)
    this.windows = {}
  }
  playbackWindows: BrowserWindow[]
  session: Session
  windowLoaders: WindowLoaderMap
  windows: { [key: string]: BrowserWindow }

  async broadcast(path: string, ...args: any) {
    Object.values(this.windows).forEach((window) => {
      window.webContents.send(path, ...args)
    })
  }

  async close(name: string): Promise<boolean> {
    const window = this.windows[name]
    if (!window) {
      return false
    }
    delete this.windows[name]
    window.close()
    return true
  }

  async closeAll(): Promise<void> {
    const playbackWindows = this.playbackWindows
    playbackWindows.forEach((window) => window.close())
    this.windows = {}
    this.playbackWindows = []
    const windows = BrowserWindow.getAllWindows()
    windows.forEach((window) => window.close())
  }

  async get(name: string): Promise<BrowserWindow> {
    return this.windows[name]
  }

  getLastPlaybackWindow(): BrowserWindow {
    return this.playbackWindows[this.playbackWindows.length - 1]
  }

  async open(
    name: string,
    opts: BrowserWindowConstructorOptions = {}
  ): Promise<boolean> {
    if (!this.windowLoaders[name]) {
      throw new Error(`Invalid window name supplied '${name}'!`)
    }
    if (this.windows[name]) {
      return false
    }
    const window = this.windowLoaders[name](opts)
    if (name === 'playback-window') {
      this.handlePlaybackWindow(window)
    }
    this.windows[name] = window
    window.on('closed', () => {
      delete this.windows[name]
    })
    return true
  }

  handlePlaybackWindow(window: BrowserWindow) {
    this.playbackWindows.push(window)
    window.webContents.insertCSS(playbackCSS)
    window.webContents.setWindowOpenHandler(() => ({
      action: 'allow',
      overrideBrowserWindowOptions: playbackWindowOptions,
    }))
    window.webContents.on('did-create-window', (win) =>
      this.handlePlaybackWindow(win)
    )

    window.on('focus', () => {
      const windowIndex = this.playbackWindows.indexOf(window)
      if (windowIndex !== this.playbackWindows.length - 1) {
        this.playbackWindows.splice(windowIndex, 1)
        this.playbackWindows.push(window)
      }
    })
    window.on('closed', () =>
      this.playbackWindows.splice(this.playbackWindows.indexOf(window), 1)
    )
  }

  async onProjectLoaded() {
    await this.close(projectEditorWindowName)
    await this.open(projectEditorWindowName)
    await this.close('splash')

    await this.close(playbackWindowName)
    await this.open(playbackWindowName)
    const playbackWindow = await this.get(playbackWindowName)
    this.handlePlaybackWindow(playbackWindow)

    const projectWindow = await this.get(projectEditorWindowName)
    playbackWindow.on('focus', () => {
      projectWindow.showInactive()
    })
    playbackWindow.on('blur', () => {
      const windows = BrowserWindow.getAllWindows()
      const anyWindowFocused = windows.reduce((focused, window) => {
        if (focused) return true
        return window.isFocused()
      }, false)
      if (!anyWindowFocused) {
        projectWindow.hide()
      }
    })
    playbackWindow.on('closed', () => {
      projectWindow.close()
    })
  }
}<commit_msg>Swapping playback and project window focus status (#1369)<commit_after>import * as windowConfigs from 'browser/windows/controller'
import { WindowConfig } from 'browser/types'
import { BrowserWindow, BrowserWindowConstructorOptions } from 'electron'
import { existsSync, readFileSync } from 'fs'
import kebabCase from 'lodash/fp/kebabCase'
import { Session } from 'main/types'
import { join } from 'path'

const playbackWindowName = 'playback-window'
const playbackCSS = readFileSync(join(__dirname, 'highlight.css'), 'utf-8')
const playbackWindowOptions = {
  webPreferences: {
    nodeIntegration: false,
    nodeIntegrationInSubFrames: true,
    preload: join(__dirname, `playback-window-preload-bundle.js`),
  },
}

const projectEditorWindowName = 'project-editor'

export type WindowLoader = (
  opts?: BrowserWindowConstructorOptions
) => BrowserWindow
export interface WindowLoaderMap {
  [key: string]: WindowLoader
}
export type WindowLoaderFactory = (session: Session) => WindowLoader
export interface WindowLoaderFactoryMap {
  [key: string]: WindowLoaderFactory
}
const windowLoaderFactoryMap: WindowLoaderFactoryMap = Object.fromEntries(
  Object.entries(windowConfigs).map(
    ([key, { window }]: [string, WindowConfig]) => {
      const filename = kebabCase(key)
      const preloadPath = join(__dirname, `${filename}-preload-bundle.js`)
      const hasPreload = existsSync(preloadPath)
      const windowLoader: WindowLoaderFactory =
        (session: Session) =>
        (options: BrowserWindowConstructorOptions = {}) => {
          const windowConfig = window(session)
          const win = new BrowserWindow({
            ...windowConfig,
            webPreferences: {
              ...windowConfig?.webPreferences ?? {}, 
              preload: hasPreload ? preloadPath : undefined,
            },
            ...options,
          })
          win.loadFile(join(__dirname, `${filename}.html`))
          return win
        }
      return [key, windowLoader]
    }
  )
)

const makeWindowLoaders = (session: Session): WindowLoaderMap =>
  Object.fromEntries(
    Object.entries(windowLoaderFactoryMap).map(([key, loader]) => [
      kebabCase(key),
      loader(session),
    ])
  )

export default class WindowsController {
  constructor(session: Session) {
    this.session = session
    this.playbackWindows = []
    this.windowLoaders = makeWindowLoaders(session)
    this.windows = {}
  }
  playbackWindows: BrowserWindow[]
  session: Session
  windowLoaders: WindowLoaderMap
  windows: { [key: string]: BrowserWindow }

  async broadcast(path: string, ...args: any) {
    Object.values(this.windows).forEach((window) => {
      window.webContents.send(path, ...args)
    })
  }

  async close(name: string): Promise<boolean> {
    const window = this.windows[name]
    if (!window) {
      return false
    }
    delete this.windows[name]
    window.close()
    return true
  }

  async closeAll(): Promise<void> {
    const playbackWindows = this.playbackWindows
    playbackWindows.forEach((window) => window.close())
    this.windows = {}
    this.playbackWindows = []
    const windows = BrowserWindow.getAllWindows()
    windows.forEach((window) => window.close())
  }

  async get(name: string): Promise<BrowserWindow> {
    return this.windows[name]
  }

  getLastPlaybackWindow(): BrowserWindow {
    return this.playbackWindows[this.playbackWindows.length - 1]
  }

  async open(
    name: string,
    opts: BrowserWindowConstructorOptions = {}
  ): Promise<boolean> {
    if (!this.windowLoaders[name]) {
      throw new Error(`Invalid window name supplied '${name}'!`)
    }
    if (this.windows[name]) {
      return false
    }
    const window = this.windowLoaders[name](opts)
    if (name === 'playback-window') {
      this.handlePlaybackWindow(window)
    }
    this.windows[name] = window
    window.on('closed', () => {
      delete this.windows[name]
    })
    return true
  }

  handlePlaybackWindow(window: BrowserWindow) {
    this.playbackWindows.push(window)
    window.webContents.insertCSS(playbackCSS)
    window.webContents.setWindowOpenHandler(() => ({
      action: 'allow',
      overrideBrowserWindowOptions: playbackWindowOptions,
    }))
    window.webContents.on('did-create-window', (win) =>
      this.handlePlaybackWindow(win)
    )

    window.on('focus', () => {
      const windowIndex = this.playbackWindows.indexOf(window)
      if (windowIndex !== this.playbackWindows.length - 1) {
        this.playbackWindows.splice(windowIndex, 1)
        this.playbackWindows.push(window)
      }
    })
    window.on('closed', () =>
      this.playbackWindows.splice(this.playbackWindows.indexOf(window), 1)
    )
  }

  async onProjectLoaded() {
    await this.close(projectEditorWindowName)
    await this.open(projectEditorWindowName)
    await this.close('splash')

    await this.close(playbackWindowName)
    await this.open(playbackWindowName)
    const playbackWindow = await this.get(playbackWindowName)
    this.handlePlaybackWindow(playbackWindow)

    const projectWindow = await this.get(projectEditorWindowName)
    projectWindow.on('focus', () => {
      playbackWindow.showInactive()
    })
    projectWindow.on('blur', () => {
      const windows = BrowserWindow.getAllWindows()
      const anyWindowFocused = windows.reduce((focused, window) => {
        if (focused) return true
        return window.isFocused()
      }, false)
      if (!anyWindowFocused) {
        playbackWindow.hide()
      }
    })
    projectWindow.on('closed', () => {
      playbackWindow.close()
    })
  }
}
{'fraction_non_alphanumeric': 0.12218214607754734, 'fraction_numerical': 0.0012623985572587917, 'mean_word_length': 3.3425998433829287, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 49, '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': '7590347', 'n_tokens_mistral': 3236, 'n_tokens_neox': 3149, 'n_words': 625}