xcv58/Tab-Manager-v2

View on GitHub
packages/extension/src/js/stores/FocusStore.tsx

Summary

Maintainability
A
2 hrs
Test Coverage
File `FocusStore.tsx` has 254 lines of code (exceeds 250 allowed). Consider refactoring.
import { makeAutoObservable } from 'mobx'
import Store from 'stores'
import log from 'libs/log'
import Tab from './Tab'
import Window from './Window'
import Focusable from './Focusable'
import { MutableRefObject } from 'react'
 
const getNextItem = (
items: any[],
index: number,
direction: number,
side = false,
) => {
let nextIndex = index + direction + items.length
if (side) {
nextIndex = direction * (items.length - 1)
}
return items[nextIndex % items.length]
}
 
const getFocusableItems = (win: Window, focusedItem: Focusable) => {
if (win.hide || win === focusedItem) {
return [win, ...win.matchedTabs]
}
return win.matchedTabs
}
 
export default class FocusStore {
store: Store
 
constructor(store: Store) {
makeAutoObservable(this)
 
this.store = store
}
 
get focusedItem(): Focusable | null {
const { windows, tabs } = this.store.windowStore
if (this.focusedTabId) {
return tabs.find((x) => x.id === this.focusedTabId && x.isVisible)
} else if (this.focusedWindowId) {
return windows.find((x) => x.id === this.focusedWindowId)
}
}
 
focusedWindowId: number = null
 
focusedTabId: number = null
 
containerRef: MutableRefObject<HTMLElement> = null
 
setContainerRef = (ref: MutableRefObject<HTMLElement>) => {
this.containerRef = ref
}
 
_top = -1
 
_updateTop = (top: number) => {
this._top = Math.max(top, this._top)
}
 
_setFocusedItem = (item: Focusable) => {
log.debug('_setFocusedItem:', item)
if (item instanceof Window) {
this.focusedTabId = null
this.focusedWindowId = item.id
} else if (item instanceof Tab) {
this.focusedTabId = item.id
this.focusedWindowId = null
} else {
log.error(
'invalid input item for _setFocusedItem, it is not Window nor Tab:',
{ item },
)
}
}
 
defocus = () => {
this.focusedTabId = null
this.focusedWindowId = null
this._top = -1
}
 
enter = () => {
if (this.focusedItem) {
this.focusedItem.activate()
}
}
 
focus = (item: Focusable) => {
this._setFocusedItem(item)
}
 
// Toggle select of focused tab, or the focused window.tabs
select = () => {
if (this.focusedItem) {
this.focusedItem.select()
}
}
 
selectTabsInSameContainer =
process.env.TARGET_BROWSER === 'firefox'
? () => {
if (this.focusedItem instanceof Tab) {
this.store.tabStore.selectTabsInSameContainer(this.focusedItem)
}
}
: () => {}
 
closeWindow = () => {
if (this.focusedItem) {
this.focusedItem.closeWindow()
}
}
 
selectWindow = () => {
if (this.focusedItem) {
this.focusedItem.toggleSelectAll()
}
}
 
groupTab = () => {
const { focusedItem } = this
if (!focusedItem) {
return
}
if (focusedItem instanceof Tab) {
focusedItem.groupTab()
}
}
 
_getGrid = (focusedItem: Focusable) => {
const { scrollTop } = this.containerRef.current
const { windows } = this.store.windowStore
const grid: Focusable[][] = []
let columnIndex = -1
for (const win of windows) {
const items = getFocusableItems(win, focusedItem)
for (const item of items) {
const rect = item.getBoundingClientRect()
if (!rect) {
continue
}
if (!grid.length) {
grid.push([item])
} else {
const column = grid[grid.length - 1]
const previousItem = column[column.length - 1]
if (previousItem.getBoundingClientRect().left === rect.left) {
column.push(item)
} else {
grid.push([item])
}
}
if (item === focusedItem) {
columnIndex = grid.length - 1
}
}
}
return { grid, columnIndex, scrollTop, targetColumn: grid[columnIndex] }
}
 
_moveVertically = (direction: number, side = false) => {
const { focusedItem } = this
log.debug('_moveVertically:', { direction, side, focusedItem })
if (!focusedItem) {
return this._focusOnFirstItem()
}
const { scrollTop, targetColumn } = this._getGrid(focusedItem)
if (!targetColumn) {
return log.debug('_moveVertically: No targetColumn found')
}
const index = targetColumn.findIndex((x) => x === this.focusedItem)
const item = getNextItem(targetColumn, index, direction, side)
if (!item) {
return log.error('_moveHorizontally: no available item found')
}
this._top = scrollTop + item.getBoundingClientRect().top
log.debug('_moveVertically target item:', item)
this._setFocusedItem(item)
}
 
_moveHorizontally = (direction: number) => {
const { focusedItem } = this
log.debug('_moveHorizontally:', { direction, focusedItem })
if (!focusedItem) {
return this._focusOnFirstItem()
}
const baseRect = focusedItem.getBoundingClientRect()
log.debug('_moveHorizontally baseRect:', baseRect)
if (!baseRect) {
return
}
const { grid, columnIndex, scrollTop } = this._getGrid(focusedItem)
log.debug('_moveHorizontally grid:', { grid, columnIndex, scrollTop })
if (columnIndex === -1) {
return log.error('_moveHorizontally: no available grid')
}
this._updateTop(scrollTop + baseRect.top)
const targetColumn = getNextItem(grid, columnIndex, direction)
if (!targetColumn) {
return log.error('_moveHorizontally: no target column')
}
let min = Number.MAX_VALUE
let targetItem = null
for (const item of targetColumn) {
const delta = Math.abs(
scrollTop + item.getBoundingClientRect().top - this._top,
)
if (delta < min) {
targetItem = item
min = delta
}
}
if (targetItem) {
this._setFocusedItem(targetItem)
}
}
 
left = () => {
log.debug('left')
this._moveHorizontally(-1)
}
 
right = () => {
log.debug('right')
this._moveHorizontally(1)
}
 
up = () => {
log.debug('up')
this._moveVertically(-1)
}
 
down = () => {
log.debug('down')
this._moveVertically(1)
}
 
firstTab = () => {
log.debug('firstTab')
this._moveVertically(0, true)
}
 
lastTab = () => {
log.debug('lastTab')
this._moveVertically(1, true)
}
 
_focusOnFirstItem = () => {
const { matchedTabs } = this.store.searchStore
if (matchedTabs.length) {
return this._setFocusedItem(matchedTabs[0])
}
const { windows } = this.store.windowStore
if (windows.length) {
this._setFocusedItem(windows[0])
}
}
 
setDefaultFocusedTab = () => {
log.debug('setDefaultFocusedTab:', { focusedItem: this.focusedItem })
if (this.focusedItem) {
return
}
const { lastFocusedWindow } = this.store.windowStore
if (!lastFocusedWindow) {
return log.debug('setDefaultFocusedTab no lastFocusedWindow:', {
lastFocusedWindow,
})
}
if (lastFocusedWindow.hide) {
return this.focus(lastFocusedWindow)
}
const tab = lastFocusedWindow.tabs.find((x) => x.active && x.isMatched)
log.debug('setDefaultFocusedTab active tab:', { tab })
if (tab) {
this.focus(tab)
}
}
 
toggleHideForFocusedWindow = () => {
if (this.focusedItem) {
this.focusedItem.toggleHide()
}
}
}