refactor: no import * as from react

This commit is contained in:
EnixCoda 2024-09-23 18:38:43 +08:00
parent c3de9ad835
commit 03e3454afc
73 changed files with 250 additions and 251 deletions

View file

@ -2,7 +2,7 @@ import { useConfigs } from 'containers/ConfigsContext'
import { GITHUB_OAUTH } from 'env' import { GITHUB_OAUTH } from 'env'
import { platform } from 'platforms' import { platform } from 'platforms'
import { GitHub } from 'platforms/GitHub' import { GitHub } from 'platforms/GitHub'
import * as React from 'react' import React from 'react'
export function AccessDeniedDescription() { export function AccessDeniedDescription() {
const { accessToken } = useConfigs().value const { accessToken } = useConfigs().value

View file

@ -1,4 +1,4 @@
import * as React from 'react' import React, { useEffect, useRef, useState } from 'react'
import { cx } from 'utils/cx' import { cx } from 'utils/cx'
import { copyElementContent } from 'utils/DOMHelper' import { copyElementContent } from 'utils/DOMHelper'
@ -10,8 +10,8 @@ const className = 'clippy-wrapper'
export const ClippyClassName = className export const ClippyClassName = className
export function Clippy({ codeSnippetElement }: Props) { export function Clippy({ codeSnippetElement }: Props) {
const [state, setState] = React.useState<'normal' | 'success' | 'fail'>('normal') const [state, setState] = useState<'normal' | 'success' | 'fail'>('normal')
React.useEffect(() => { useEffect(() => {
const timer = window.setTimeout(() => { const timer = window.setTimeout(() => {
setState('normal') setState('normal')
}, 1000) }, 1000)
@ -21,8 +21,8 @@ export function Clippy({ codeSnippetElement }: Props) {
// Temporary fix: // Temporary fix:
// React moved root node of event delegation since v17 // React moved root node of event delegation since v17
// onClick on <a /> won't work when rendered with `renderReact` // onClick on <a /> won't work when rendered with `renderReact`
const elementRef = React.useRef<HTMLButtonElement | null>(null) const elementRef = useRef<HTMLButtonElement | null>(null)
React.useEffect(() => { useEffect(() => {
const element = elementRef.current const element = elementRef.current
if (element) { if (element) {
const onClippyClick = () => const onClippyClick = () =>

View file

@ -5,7 +5,7 @@ import {
DiffRemovedIcon, DiffRemovedIcon,
DiffRenamedIcon, DiffRenamedIcon,
} from '@primer/octicons-react' } from '@primer/octicons-react'
import * as React from 'react' import React from 'react'
import { resolveDiffGraphMeta } from 'utils/general' import { resolveDiffGraphMeta } from 'utils/general'
import { Icon } from '../Icon' import { Icon } from '../Icon'

View file

@ -1,4 +1,4 @@
import * as React from 'react' import React from 'react'
import { Icon } from '../Icon' import { Icon } from '../Icon'
const iconMap = { const iconMap = {

View file

@ -1,6 +1,6 @@
import { useConfigs } from 'containers/ConfigsContext' import { useConfigs } from 'containers/ConfigsContext'
import { platform } from 'platforms' import { platform } from 'platforms'
import * as React from 'react' import React, { useMemo, useRef } from 'react'
import { cx } from 'utils/cx' import { cx } from 'utils/cx'
import { cancelEvent } from 'utils/DOMHelper' import { cancelEvent } from 'utils/DOMHelper'
import { getFileIconURL, getFolderIconURL } from 'utils/parseIconMapCSV' import { getFileIconURL, getFolderIconURL } from 'utils/parseIconMapCSV'
@ -43,7 +43,7 @@ export const Node = React.memo(function Node({
onFocus, onFocus,
}: Props) { }: Props) {
const { compactFileTree: compact } = useConfigs().value const { compactFileTree: compact } = useConfigs().value
const ref = React.useRef<HTMLDivElement>(null) const ref = useRef<HTMLDivElement>(null)
return ( return (
<a <a
href={node.url} href={node.url}
@ -87,11 +87,11 @@ const NodeItemIcon = React.memo(function NodeItemIcon({
}) { }) {
const { icons } = useConfigs().value const { icons } = useConfigs().value
const src = React.useMemo( const src = useMemo(
() => (node.type === 'tree' ? getFolderIconURL(node, open) : getFileIconURL(node)), () => (node.type === 'tree' ? getFolderIconURL(node, open) : getFileIconURL(node)),
[node, open], [node, open],
) )
const iconType = React.useMemo(() => getIconType(node), [node]) const iconType = useMemo(() => getIconType(node), [node])
if (icons === 'native') return <Icon type={iconType} /> if (icons === 'native') return <Icon type={iconType} />
return ( return (

View file

@ -1,8 +1,8 @@
import * as React from 'react' import { useCallback } from 'react'
import { VisibleNodesGenerator } from 'utils/VisibleNodesGenerator' import { VisibleNodesGenerator } from 'utils/VisibleNodesGenerator'
export function useExpandTo(visibleNodesGenerator: VisibleNodesGenerator) { export function useExpandTo(visibleNodesGenerator: VisibleNodesGenerator) {
return React.useCallback( return useCallback(
async (currentPath: string[]) => { async (currentPath: string[]) => {
const nodeExpandedTo = await visibleNodesGenerator.expandTo(currentPath.join('/')) const nodeExpandedTo = await visibleNodesGenerator.expandTo(currentPath.join('/'))
if (nodeExpandedTo) visibleNodesGenerator.focusNode(nodeExpandedTo) if (nodeExpandedTo) visibleNodesGenerator.focusNode(nodeExpandedTo)

View file

@ -1,8 +1,8 @@
import * as React from 'react' import { useCallback } from 'react'
import { VisibleNodesGenerator } from 'utils/VisibleNodesGenerator' import { VisibleNodesGenerator } from 'utils/VisibleNodesGenerator'
export function useFocusNode(visibleNodesGenerator: VisibleNodesGenerator) { export function useFocusNode(visibleNodesGenerator: VisibleNodesGenerator) {
return React.useCallback( return useCallback(
(node: TreeNode | null) => visibleNodesGenerator.focusNode(node), (node: TreeNode | null) => visibleNodesGenerator.focusNode(node),
[visibleNodesGenerator], [visibleNodesGenerator],
) )

View file

@ -1,4 +1,4 @@
import * as React from 'react' import React, { useCallback } from 'react'
import { VisibleNodesGenerator } from 'utils/VisibleNodesGenerator' import { VisibleNodesGenerator } from 'utils/VisibleNodesGenerator'
import { useExpandTo } from './useExpandTo' import { useExpandTo } from './useExpandTo'
@ -7,7 +7,7 @@ export function useGoTo(
updateSearchKey: React.Dispatch<React.SetStateAction<string>>, updateSearchKey: React.Dispatch<React.SetStateAction<string>>,
expandTo: ReturnType<typeof useExpandTo>, expandTo: ReturnType<typeof useExpandTo>,
) { ) {
return React.useCallback( return useCallback(
(path: string[]) => { (path: string[]) => {
updateSearchKey('') updateSearchKey('')
visibleNodesGenerator.search(null) visibleNodesGenerator.search(null)

View file

@ -1,5 +1,5 @@
import { SidebarContext } from 'components/SidebarContext' import { SidebarContext } from 'components/SidebarContext'
import * as React from 'react' import React, { useCallback, useContext } from 'react'
import * as DOMHelper from 'utils/DOMHelper' import * as DOMHelper from 'utils/DOMHelper'
import { OperatingSystems, os } from 'utils/general' import { OperatingSystems, os } from 'utils/general'
import { loadWithFastRedirect } from 'utils/hooks/useFastRedirect' import { loadWithFastRedirect } from 'utils/hooks/useFastRedirect'
@ -32,9 +32,9 @@ export function useHandleKeyDown(
searched: boolean, searched: boolean,
setAlignMode: (mode: AlignMode) => void, setAlignMode: (mode: AlignMode) => void,
) { ) {
const { pendingFocusTarget } = React.useContext(SidebarContext) const { pendingFocusTarget } = useContext(SidebarContext)
const setPendingFocusTarget = pendingFocusTarget.onChange const setPendingFocusTarget = pendingFocusTarget.onChange
return React.useCallback( return useCallback(
(event: React.KeyboardEvent<HTMLElement>) => { (event: React.KeyboardEvent<HTMLElement>) => {
const { nodes, focusedNode, expandedNodes } = visibleNodes const { nodes, focusedNode, expandedNodes } = visibleNodes

View file

@ -8,7 +8,7 @@ import { ActionList, AnchoredOverlay } from '@primer/react'
import { useConfigs } from 'containers/ConfigsContext' import { useConfigs } from 'containers/ConfigsContext'
import { PortalContext } from 'containers/PortalContext' import { PortalContext } from 'containers/PortalContext'
import { platform } from 'platforms' import { platform } from 'platforms'
import * as React from 'react' import React, { useCallback, useContext, useMemo, useState } from 'react'
import { useCopyToClipboard } from 'react-use' import { useCopyToClipboard } from 'react-use'
import { cx } from 'utils/cx' import { cx } from 'utils/cx'
import { cancelEvent, onEnterKeyDown } from 'utils/DOMHelper' import { cancelEvent, onEnterKeyDown } from 'utils/DOMHelper'
@ -22,7 +22,7 @@ import { VisibleNodesGeneratorMethods } from './useVisibleNodesGeneratorMethods'
export type NodeRenderer = (node: TreeNode) => React.ReactNode export type NodeRenderer = (node: TreeNode) => React.ReactNode
export function useNodeRenderers(allRenderers: (NodeRenderer | null | undefined)[]) { export function useNodeRenderers(allRenderers: (NodeRenderer | null | undefined)[]) {
return React.useMemo(() => { return useMemo(() => {
const renderers: NodeRenderer[] = allRenderers.filter(is.not.nil) const renderers: NodeRenderer[] = allRenderers.filter(is.not.nil)
return renderers.length return renderers.length
? (node: TreeNode) => ? (node: TreeNode) =>
@ -33,7 +33,7 @@ export function useNodeRenderers(allRenderers: (NodeRenderer | null | undefined)
export function useRenderFileStatus() { export function useRenderFileStatus() {
const { showDiffInText } = useConfigs().value const { showDiffInText } = useConfigs().value
return React.useCallback( return useCallback(
function renderFileStatus({ diff }: TreeNode) { function renderFileStatus({ diff }: TreeNode) {
return ( return (
diff && ( diff && (
@ -64,10 +64,10 @@ function NodeContextMenu({
node: TreeNode node: TreeNode
visibleNodesGeneratorMethods: VisibleNodesGeneratorMethods visibleNodesGeneratorMethods: VisibleNodesGeneratorMethods
}) { }) {
const [isOpen, setIsOpen] = React.useState(false) const [isOpen, setIsOpen] = useState(false)
const [copied, setCopied] = React.useState<string | null>(null) const [copied, setCopied] = useState<string | null>(null)
const [copyState, copyToClipboard] = useCopyToClipboard() const [copyState, copyToClipboard] = useCopyToClipboard()
const portalName = React.useContext(PortalContext) const portalName = useContext(PortalContext)
const actionElements = { const actionElements = {
copyPermalink: copyPermalink:
node.permalink && node.permalink &&
@ -233,14 +233,14 @@ export function useRenderFileCommentAmounts() {
) : null ) : null
} }
const { commentToggle } = useConfigs().value const { commentToggle } = useConfigs().value
return React.useMemo(() => (commentToggle ? renderFileCommentAmounts : null), [commentToggle]) return useMemo(() => (commentToggle ? renderFileCommentAmounts : null), [commentToggle])
} }
export function useRenderFindInFolderButton( export function useRenderFindInFolderButton(
onSearch: (searchKey: string, searchMode: SearchMode) => void, onSearch: (searchKey: string, searchMode: SearchMode) => void,
) { ) {
const { searchMode } = useConfigs().value const { searchMode } = useConfigs().value
return React.useMemo( return useMemo(
() => () =>
searchMode === 'fuzzy' searchMode === 'fuzzy'
? function renderFindInFolderButton(node: TreeNode) { ? function renderFindInFolderButton(node: TreeNode) {
@ -260,7 +260,7 @@ export function useRenderFindInFolderButton(
} }
export function useRenderGoToButton(searched: boolean, goTo: (path: string[]) => void) { export function useRenderGoToButton(searched: boolean, goTo: (path: string[]) => void) {
return React.useMemo( return useMemo(
() => () =>
searched searched
? function renderGoToButton(node: TreeNode): React.ReactNode { ? function renderGoToButton(node: TreeNode): React.ReactNode {

View file

@ -1,5 +1,5 @@
import { useConfigs } from 'containers/ConfigsContext' import { useConfigs } from 'containers/ConfigsContext'
import * as React from 'react' import React, { useCallback } from 'react'
import { isOpenInNewWindowClick } from 'utils/general' import { isOpenInNewWindowClick } from 'utils/general'
import { loadWithFastRedirect } from 'utils/hooks/useFastRedirect' import { loadWithFastRedirect } from 'utils/hooks/useFastRedirect'
import { AlignMode } from '../useVirtualScroll' import { AlignMode } from '../useVirtualScroll'
@ -10,7 +10,7 @@ export function useHandleNodeClick(
setAlignMode: (mode: AlignMode) => void, setAlignMode: (mode: AlignMode) => void,
) { ) {
const { recursiveToggleFolder } = useConfigs().value const { recursiveToggleFolder } = useConfigs().value
return React.useCallback( return useCallback(
(event: React.MouseEvent<HTMLElement, MouseEvent>, node: TreeNode) => { (event: React.MouseEvent<HTMLElement, MouseEvent>, node: TreeNode) => {
setAlignMode('lazy') setAlignMode('lazy')
switch (node.type) { switch (node.type) {

View file

@ -1,5 +1,5 @@
import { useConfigs } from 'containers/ConfigsContext' import { useConfigs } from 'containers/ConfigsContext'
import * as React from 'react' import { useCallback } from 'react'
import { VisibleNodesGenerator } from 'utils/VisibleNodesGenerator' import { VisibleNodesGenerator } from 'utils/VisibleNodesGenerator'
import { SearchMode, searchModes } from '../../searchModes' import { SearchMode, searchModes } from '../../searchModes'
@ -8,7 +8,7 @@ export function useOnSearch(
visibleNodesGenerator: VisibleNodesGenerator, visibleNodesGenerator: VisibleNodesGenerator,
) { ) {
const { restoreExpandedFolders } = useConfigs().value const { restoreExpandedFolders } = useConfigs().value
return React.useCallback( return useCallback(
(searchKey: string, searchMode: SearchMode) => { (searchKey: string, searchMode: SearchMode) => {
updateSearchKey(searchKey) updateSearchKey(searchKey)
visibleNodesGenerator.search( visibleNodesGenerator.search(

View file

@ -1,10 +1,10 @@
import { useConfigs } from 'containers/ConfigsContext' import { useConfigs } from 'containers/ConfigsContext'
import * as React from 'react' import { useCallback } from 'react'
import { searchModes } from '../../searchModes' import { searchModes } from '../../searchModes'
export function useRenderLabelText(searchKey: string) { export function useRenderLabelText(searchKey: string) {
const { searchMode } = useConfigs().value const { searchMode } = useConfigs().value
return React.useCallback( return useCallback(
(node: TreeNode) => searchModes[searchMode].renderNodeLabelText(node, searchKey), (node: TreeNode) => searchModes[searchMode].renderNodeLabelText(node, searchKey),
[searchKey, searchMode], [searchKey, searchMode],
) )

View file

@ -1,8 +1,8 @@
import * as React from 'react' import { useCallback } from 'react'
import { VisibleNodesGenerator } from 'utils/VisibleNodesGenerator' import { VisibleNodesGenerator } from 'utils/VisibleNodesGenerator'
export function useToggleExpansion(visibleNodesGenerator: VisibleNodesGenerator) { export function useToggleExpansion(visibleNodesGenerator: VisibleNodesGenerator) {
return React.useCallback( return useCallback(
async ( async (
node: TreeNode, node: TreeNode,
{ {

View file

@ -3,10 +3,18 @@ import { useFocusOnPendingTarget } from 'components/FocusTarget'
import { LoadingIndicator } from 'components/LoadingIndicator' import { LoadingIndicator } from 'components/LoadingIndicator'
import { SearchBar } from 'components/SearchBar' import { SearchBar } from 'components/SearchBar'
import { useConfigs } from 'containers/ConfigsContext' import { useConfigs } from 'containers/ConfigsContext'
import { useInspector } from 'containers/Inspector'
import { PortalContext } from 'containers/PortalContext' import { PortalContext } from 'containers/PortalContext'
import { RepoContext } from 'containers/RepoContext' import { RepoContext } from 'containers/RepoContext'
import { useInspector } from 'containers/Inspector' import React, {
import * as React from 'react' useCallback,
useContext,
useEffect,
useLayoutEffect,
useMemo,
useRef,
useState,
} from 'react'
import { usePrevious, useUpdateEffect } from 'react-use' import { usePrevious, useUpdateEffect } from 'react-use'
import { cx } from 'utils/cx' import { cx } from 'utils/cx'
import { run } from 'utils/general' import { run } from 'utils/general'
@ -46,7 +54,7 @@ export type NodeRendererContext = {
} }
export function FileExplorer() { export function FileExplorer() {
const metaData = React.useContext(RepoContext) const metaData = useContext(RepoContext)
const visibleNodesGenerator = useVisibleNodesGenerator(metaData) const visibleNodesGenerator = useVisibleNodesGenerator(metaData)
const visibleNodes = useVisibleNodes(visibleNodesGenerator) const visibleNodes = useVisibleNodes(visibleNodesGenerator)
const state = useLoadedContext(SideBarStateContext).value const state = useLoadedContext(SideBarStateContext).value
@ -88,7 +96,7 @@ function LoadedFileExplorer({
}) { }) {
const config = useConfigs().value const config = useConfigs().value
const [searchKey, updateSearchKey] = React.useState('') const [searchKey, updateSearchKey] = useState('')
const searched = !!searchKey const searched = !!searchKey
const onSearch = useOnSearch(updateSearchKey, visibleNodesGenerator) const onSearch = useOnSearch(updateSearchKey, visibleNodesGenerator)
const { focusedNode, nodes, expandedNodes, depths, loading } = visibleNodes const { focusedNode, nodes, expandedNodes, depths, loading } = visibleNodes
@ -117,8 +125,8 @@ function LoadedFileExplorer({
overScan: 10, overScan: 10,
}) })
const portalName = React.useMemo(() => `${Math.random()}`, []) const portalName = useMemo(() => `${Math.random()}`, [])
React.useEffect(() => { useEffect(() => {
const current = scrollElementRef.current const current = scrollElementRef.current
if (current) registerPortalRoot(current, portalName) if (current) registerPortalRoot(current, portalName)
}, [scrollElementRef, portalName]) }, [scrollElementRef, portalName])
@ -133,18 +141,18 @@ function LoadedFileExplorer({
// - "lazy" // - "lazy"
// - navigate with keyboard // - navigate with keyboard
// - "lazy" // - "lazy"
const [alignMode, setAlignMode] = React.useState<AlignMode>('top') const [alignMode, setAlignMode] = useState<AlignMode>('top')
const index = React.useMemo( const index = useMemo(
() => (focusedNode?.path ? nodes.findIndex(node => node.path === focusedNode.path) : -1), () => (focusedNode?.path ? nodes.findIndex(node => node.path === focusedNode.path) : -1),
[focusedNode?.path, nodes], [focusedNode?.path, nodes],
) )
React.useLayoutEffect(() => { useLayoutEffect(() => {
if (index !== -1) scrollToItem(index, alignMode) if (index !== -1) scrollToItem(index, alignMode)
}, [index, scrollToItem, alignMode]) }, [index, scrollToItem, alignMode])
const prevSearchKey = usePrevious(searchKey) const prevSearchKey = usePrevious(searchKey)
React.useEffect(() => { useEffect(() => {
// when start searching or stop searching // when start searching or stop searching
if (!prevSearchKey !== !searchKey) scrollToItem(0, alignMode) if (!prevSearchKey !== !searchKey) scrollToItem(0, alignMode)
}, [prevSearchKey, searchKey, scrollToItem, alignMode]) }, [prevSearchKey, searchKey, scrollToItem, alignMode])
@ -170,7 +178,7 @@ function LoadedFileExplorer({
]) ])
const renderLabelText = useRenderLabelText(searchKey) const renderLabelText = useRenderLabelText(searchKey)
const goToCurrentItem = React.useCallback(() => { const goToCurrentItem = useCallback(() => {
const targetPath = getCurrentPath() const targetPath = getCurrentPath()
if (targetPath) expandTo(targetPath) if (targetPath) expandTo(targetPath)
}, [getCurrentPath, expandTo]) }, [getCurrentPath, expandTo])
@ -178,14 +186,14 @@ function LoadedFileExplorer({
useOnLocationChange(goToCurrentItem) useOnLocationChange(goToCurrentItem)
useAfterRedirect(goToCurrentItem) useAfterRedirect(goToCurrentItem)
const [currentPath, setCurrentPath] = React.useState(() => getCurrentPath()) const [currentPath, setCurrentPath] = useState(() => getCurrentPath())
useAfterRedirect(React.useCallback(() => setCurrentPath(getCurrentPath()), [getCurrentPath])) useAfterRedirect(useCallback(() => setCurrentPath(getCurrentPath()), [getCurrentPath]))
useInspector('CurrentPath', currentPath) useInspector('CurrentPath', currentPath)
const ref = React.useRef<HTMLDivElement | null>(null) const ref = useRef<HTMLDivElement | null>(null)
useFocusOnPendingTarget( useFocusOnPendingTarget(
'files', 'files',
React.useCallback(() => ref.current?.focus(), []), useCallback(() => ref.current?.focus(), []),
) )
return ( return (

View file

@ -1,4 +1,4 @@
import * as React from 'react' import React, { useCallback } from 'react'
import { VisibleNodesGeneratorMethods } from './hooks/useVisibleNodesGeneratorMethods' import { VisibleNodesGeneratorMethods } from './hooks/useVisibleNodesGeneratorMethods'
import { AlignMode } from './useVirtualScroll' import { AlignMode } from './useVirtualScroll'
@ -6,7 +6,7 @@ export function useHandleNodeFocus(
{ focusNode }: VisibleNodesGeneratorMethods, { focusNode }: VisibleNodesGeneratorMethods,
setAlignMode: (mode: AlignMode) => void, setAlignMode: (mode: AlignMode) => void,
) { ) {
return React.useCallback( return useCallback(
(event: React.FocusEvent<HTMLElement, Element>, node: TreeNode) => { (event: React.FocusEvent<HTMLElement, Element>, node: TreeNode) => {
setAlignMode('lazy') setAlignMode('lazy')
focusNode(node) focusNode(node)

View file

@ -1,8 +1,8 @@
import * as React from 'react' import { useCallback, useEffect, useRef } from 'react'
function useLatestValueRef<T>(value: T) { function useLatestValueRef<T>(value: T) {
const ref = React.useRef(value) const ref = useRef(value)
React.useEffect(() => { useEffect(() => {
ref.current = value ref.current = value
}) })
return ref return ref
@ -11,5 +11,5 @@ export function useCallbackRef<Args extends AnyArray, R>(
callback: (...args: Args) => R, callback: (...args: Args) => R,
): (...args: Args) => R { ): (...args: Args) => R {
const ref = useLatestValueRef(callback) const ref = useLatestValueRef(callback)
return React.useCallback((...args: Args) => ref.current(...args), [ref]) return useCallback((...args: Args) => ref.current(...args), [ref])
} }

View file

@ -1,4 +1,4 @@
import * as React from 'react' import React, { useCallback, useMemo, useRef, useState } from 'react'
import { useCallbackRef } from './useLatestValueRef' import { useCallbackRef } from './useLatestValueRef'
function memoize<Args extends AnyArray, R>( function memoize<Args extends AnyArray, R>(
@ -29,14 +29,14 @@ export function useVirtualScroll<E extends HTMLElement>({
}) { }) {
const totalHeight = totalAmount * rowHeight const totalHeight = totalAmount * rowHeight
const ref = React.useRef<E | null>(null) // TODO: compare DOM native event listener const ref = useRef<E | null>(null) // TODO: compare DOM native event listener
const [scrollTop, setScrollTop] = React.useState(0) const [scrollTop, setScrollTop] = useState(0)
const onScroll = React.useCallback((e: React.UIEvent<E, UIEvent>) => { const onScroll = useCallback((e: React.UIEvent<E, UIEvent>) => {
setScrollTop(e.currentTarget.scrollTop) setScrollTop(e.currentTarget.scrollTop)
}, []) }, [])
const [startRenderIndex, endRenderIndex] = React.useMemo(() => { const [startRenderIndex, endRenderIndex] = useMemo(() => {
const viewportLastItemOverflow = viewportHeight % rowHeight const viewportLastItemOverflow = viewportHeight % rowHeight
const visibleRowCount = (viewportHeight - viewportLastItemOverflow) / rowHeight const visibleRowCount = (viewportHeight - viewportLastItemOverflow) / rowHeight
const inViewIndexFirst = (Math.min(scrollTop, totalHeight - viewportHeight) / rowHeight) >> 0 const inViewIndexFirst = (Math.min(scrollTop, totalHeight - viewportHeight) / rowHeight) >> 0
@ -46,14 +46,14 @@ export function useVirtualScroll<E extends HTMLElement>({
return [renderIndexFirst, renderIndexLast] return [renderIndexFirst, renderIndexLast]
}, [scrollTop, viewportHeight, overScan, rowHeight, totalAmount, totalHeight]) }, [scrollTop, viewportHeight, overScan, rowHeight, totalAmount, totalHeight])
const indexes = React.useMemo(() => { const indexes = useMemo(() => {
const indexes: number[] = [] const indexes: number[] = []
let i = startRenderIndex let i = startRenderIndex
while (i < endRenderIndex) indexes.push(i++) while (i < endRenderIndex) indexes.push(i++)
return indexes return indexes
}, [startRenderIndex, endRenderIndex]) }, [startRenderIndex, endRenderIndex])
const mapStyles = React.useCallback( const mapStyles = useCallback(
(row: number): React.CSSProperties => ({ (row: number): React.CSSProperties => ({
position: 'absolute', position: 'absolute',
top: 0, top: 0,
@ -63,9 +63,9 @@ export function useVirtualScroll<E extends HTMLElement>({
}), }),
[rowHeight], [rowHeight],
) )
const memoizedStyler = React.useMemo(() => memoize(mapStyles, row => row), [mapStyles]) const memoizedStyler = useMemo(() => memoize(mapStyles, row => row), [mapStyles])
const visibleRows: { row: number; style: React.CSSProperties }[] = React.useMemo( const visibleRows: { row: number; style: React.CSSProperties }[] = useMemo(
() => () =>
indexes.map(row => ({ indexes.map(row => ({
row, row,
@ -74,7 +74,7 @@ export function useVirtualScroll<E extends HTMLElement>({
[indexes, memoizedStyler], [indexes, memoizedStyler],
) )
const containerStyle: React.CSSProperties = React.useMemo( const containerStyle: React.CSSProperties = useMemo(
() => ({ () => ({
height: totalHeight, height: totalHeight,
position: 'relative', position: 'relative',

View file

@ -1,11 +1,11 @@
import * as React from 'react' import { useContext, useEffect } from 'react'
import { SidebarContext } from './SidebarContext' import { SidebarContext } from './SidebarContext'
export type FocusTarget = 'files' | 'search' | null export type FocusTarget = 'files' | 'search' | null
export function useFocusOnPendingTarget(target: FocusTarget, method: () => void) { export function useFocusOnPendingTarget(target: FocusTarget, method: () => void) {
const { pendingFocusTarget } = React.useContext(SidebarContext) const { pendingFocusTarget } = useContext(SidebarContext)
React.useEffect(() => { useEffect(() => {
if (pendingFocusTarget.value === target) { if (pendingFocusTarget.value === target) {
method() method()
pendingFocusTarget.onChange(null) pendingFocusTarget.onChange(null)

View file

@ -2,7 +2,7 @@ import { GearIcon, SyncIcon } from '@primer/octicons-react'
import { Link } from '@primer/react' import { Link } from '@primer/react'
import { ReloadContext } from 'containers/ReloadContext' import { ReloadContext } from 'containers/ReloadContext'
import { VERSION } from 'env' import { VERSION } from 'env'
import * as React from 'react' import React, { useContext } from 'react'
import { RoundIconButton } from './RoundIconButton' import { RoundIconButton } from './RoundIconButton'
import { wikiLinks } from './settings/SettingsBar' import { wikiLinks } from './settings/SettingsBar'
@ -12,7 +12,7 @@ type Props = {
export function Footer(props: Props) { export function Footer(props: Props) {
const { toggleShowSettings } = props const { toggleShowSettings } = props
const reload = React.useContext(ReloadContext) const reload = useContext(ReloadContext)
return ( return (
<div className={'gitako-footer'}> <div className={'gitako-footer'}>
<div className="gitako-footer-section"> <div className="gitako-footer-section">

View file

@ -2,7 +2,7 @@ import { SideBar } from 'components/SideBar'
import { ConfigsContextWrapper } from 'containers/ConfigsContext' import { ConfigsContextWrapper } from 'containers/ConfigsContext'
import { InspectorContextWrapper } from 'containers/Inspector' import { InspectorContextWrapper } from 'containers/Inspector'
import { ReloadContextWrapper } from 'containers/ReloadContext' import { ReloadContextWrapper } from 'containers/ReloadContext'
import * as React from 'react' import React, { useMemo } from 'react'
import { StyleSheetManager } from 'styled-components' import { StyleSheetManager } from 'styled-components'
import { insertMountPoint } from 'utils/DOMHelper' import { insertMountPoint } from 'utils/DOMHelper'
import { ErrorBoundary } from '../containers/ErrorBoundary' import { ErrorBoundary } from '../containers/ErrorBoundary'
@ -12,7 +12,7 @@ import { RepoContextWrapper } from '../containers/RepoContext'
import { StateBarStateContextWrapper } from '../containers/SideBarState' import { StateBarStateContextWrapper } from '../containers/SideBarState'
export function Gitako() { export function Gitako() {
const mountPoint = React.useMemo(() => insertMountPoint(), []) const mountPoint = useMemo(() => insertMountPoint(), [])
return ( return (
<StyleSheetManager target={mountPoint}> <StyleSheetManager target={mountPoint}>
<ReloadContextWrapper> <ReloadContextWrapper>

View file

@ -1,8 +1,8 @@
import * as React from 'react' import React, { useMemo } from 'react'
import { getIsSupportedRegex } from './searchModes/regexMode' import { getIsSupportedRegex } from './searchModes/regexMode'
export const Highlight = function Highlight({ text, match }: { text: string; match?: RegExp }) { export const Highlight = function Highlight({ text, match }: { text: string; match?: RegExp }) {
const $match = React.useMemo( const $match = useMemo(
() => () =>
match instanceof RegExp match instanceof RegExp
? match.flags.includes('g') ? match.flags.includes('g')
@ -12,7 +12,7 @@ export const Highlight = function Highlight({ text, match }: { text: string; mat
[match], [match],
) )
const chunks = React.useMemo(() => getChunks(text, $match), [text, $match]) const chunks = useMemo(() => getChunks(text, $match), [text, $match])
return <>{chunks.map(([type, text], key) => React.createElement(type, { key }, text))}</> return <>{chunks.map(([type, text], key) => React.createElement(type, { key }, text))}</>
} }

View file

@ -1,4 +1,4 @@
import * as React from 'react' import React from 'react'
export function HighlightOnIndexes({ text, indexes = [] }: { text: string; indexes?: number[] }) { export function HighlightOnIndexes({ text, indexes = [] }: { text: string; indexes?: number[] }) {
return ( return (

View file

@ -3,16 +3,15 @@ import {
ChevronRightIcon as ChevronRight, ChevronRightIcon as ChevronRight,
ClockIcon as Clock, ClockIcon as Clock,
CommentIcon as Comment, CommentIcon as Comment,
DiffAddedIcon as DiffAdded,
DiffIcon as Diff, DiffIcon as Diff,
DiffAddedIcon as DiffAdded,
DiffIgnoredIcon as DiffIgnored, DiffIgnoredIcon as DiffIgnored,
DiffModifiedIcon as DiffModified, DiffModifiedIcon as DiffModified,
DiffRemovedIcon as DiffRemoved, DiffRemovedIcon as DiffRemoved,
DiffRenamedIcon as DiffRenamed, DiffRenamedIcon as DiffRenamed,
FileCodeIcon as FileCode,
FileIcon as File, FileIcon as File,
FileCodeIcon as FileCode,
FileMediaIcon as FileMedia, FileMediaIcon as FileMedia,
FileSubmoduleIcon as Submodule,
FileZipIcon as FileZip, FileZipIcon as FileZip,
GearIcon as Gear, GearIcon as Gear,
GrabberIcon as Grabber, GrabberIcon as Grabber,
@ -22,10 +21,11 @@ import {
PinIcon as Pin, PinIcon as Pin,
ReplyIcon as Reply, ReplyIcon as Reply,
SearchIcon as Search, SearchIcon as Search,
FileSubmoduleIcon as Submodule,
TabIcon as Tab, TabIcon as Tab,
XIcon as X, XIcon as X,
} from '@primer/octicons-react' } from '@primer/octicons-react'
import * as React from 'react' import React from 'react'
import { cx } from 'utils/cx' import { cx } from 'utils/cx'
const iconToComponentMap = { const iconToComponentMap = {

View file

@ -1,5 +1,5 @@
import { Checkbox as PrimerCheckbox, CheckboxProps, FormControl } from '@primer/react' import { CheckboxProps, FormControl, Checkbox as PrimerCheckbox } from '@primer/react'
import * as React from 'react' import React from 'react'
export function Checkbox({ export function Checkbox({
label, label,

View file

@ -1,5 +1,5 @@
import { FormControl, Select, SelectProps } from '@primer/react' import { FormControl, Select, SelectProps } from '@primer/react'
import * as React from 'react' import React from 'react'
export type Option<T> = { export type Option<T> = {
key: string key: string

View file

@ -1,5 +1,5 @@
import { HourglassIcon } from '@primer/octicons-react' import { HourglassIcon } from '@primer/octicons-react'
import * as React from 'react' import React from 'react'
type Props = { type Props = {
text: React.ReactNode text: React.ReactNode

View file

@ -2,11 +2,11 @@ import { GitBranchIcon } from '@primer/octicons-react'
import { Box, BranchName, Breadcrumbs, Text } from '@primer/react' import { Box, BranchName, Breadcrumbs, Text } from '@primer/react'
import { RepoContext } from 'containers/RepoContext' import { RepoContext } from 'containers/RepoContext'
import { platform } from 'platforms' import { platform } from 'platforms'
import * as React from 'react' import React, { useContext } from 'react'
import { createAnchorClickHandler } from 'utils/createAnchorClickHandler' import { createAnchorClickHandler } from 'utils/createAnchorClickHandler'
export function MetaBar() { export function MetaBar() {
const metaData = React.useContext(RepoContext) const metaData = useContext(RepoContext)
if (!metaData) return null if (!metaData) return null
const { userName, repoName, branchName } = metaData const { userName, repoName, branchName } = metaData

View file

@ -1,4 +1,4 @@
import * as React from 'react' import React from 'react'
import * as ReactDOM from 'react-dom' import * as ReactDOM from 'react-dom'
type Props = { type Props = {

View file

@ -1,6 +1,6 @@
import { GrabberIcon } from '@primer/octicons-react' import { GrabberIcon } from '@primer/octicons-react'
import { Icon } from 'components/Icon' import { Icon } from 'components/Icon'
import * as React from 'react' import React from 'react'
import { ResizeHandlerOptions, useResizeHandler } from '../utils/hooks/useResizeHandler' import { ResizeHandlerOptions, useResizeHandler } from '../utils/hooks/useResizeHandler'
import { Size2D } from './Size' import { Size2D } from './Size'

View file

@ -1,7 +1,7 @@
import { AlertIcon, SearchIcon, XIcon } from '@primer/octicons-react' import { AlertIcon, SearchIcon, XIcon } from '@primer/octicons-react'
import { Popover, Text, TextInput, TextInputProps } from '@primer/react' import { Popover, Text, TextInput, TextInputProps } from '@primer/react'
import { useConfigs } from 'containers/ConfigsContext' import { useConfigs } from 'containers/ConfigsContext'
import * as React from 'react' import React, { useCallback, useMemo, useRef } from 'react'
import { formatWithShortcut, isValidRegexpSource } from 'utils/general' import { formatWithShortcut, isValidRegexpSource } from 'utils/general'
import { useFocusOnPendingTarget } from './FocusTarget' import { useFocusOnPendingTarget } from './FocusTarget'
import { SearchMode } from './searchModes' import { SearchMode } from './searchModes'
@ -13,10 +13,10 @@ type Props = {
} & Required<Pick<TextInputProps, 'onFocus'>> } & Required<Pick<TextInputProps, 'onFocus'>>
export function SearchBar({ onSearch, onFocus, value }: Props) { export function SearchBar({ onSearch, onFocus, value }: Props) {
const ref = React.useRef<HTMLInputElement | null>(null) const ref = useRef<HTMLInputElement | null>(null)
useFocusOnPendingTarget( useFocusOnPendingTarget(
'search', 'search',
React.useCallback(() => ref.current?.focus(), []), useCallback(() => ref.current?.focus(), []),
) )
const configs = useConfigs() const configs = useConfigs()
@ -27,7 +27,7 @@ export function SearchBar({ onSearch, onFocus, value }: Props) {
? 'Match file name with regular expression.' ? 'Match file name with regular expression.'
: `Match file path sequence with plain input.` : `Match file path sequence with plain input.`
const isInputValid = React.useMemo( const isInputValid = useMemo(
() => () =>
({ ({
regex: isValidRegexpSource(value), regex: isValidRegexpSource(value),
@ -35,7 +35,7 @@ export function SearchBar({ onSearch, onFocus, value }: Props) {
}[searchMode]), }[searchMode]),
[value, searchMode], [value, searchMode],
) )
const isSupportedRegex = React.useMemo( const isSupportedRegex = useMemo(
() => !(searchMode === 'regex' && !getIsSupportedRegex(value)), () => !(searchMode === 'regex' && !getIsSupportedRegex(value)),
[value, searchMode], [value, searchMode],
) )

View file

@ -7,7 +7,7 @@ import { Portal } from 'components/Portal'
import { ToggleShowButton } from 'components/ToggleShowButton' import { ToggleShowButton } from 'components/ToggleShowButton'
import { useConfigs } from 'containers/ConfigsContext' import { useConfigs } from 'containers/ConfigsContext'
import { platform, platformName } from 'platforms' import { platform, platformName } from 'platforms'
import * as React from 'react' import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { IIFC } from 'react-iifc' import { IIFC } from 'react-iifc'
import { useWindowSize } from 'react-use' import { useWindowSize } from 'react-use'
import { Config } from 'utils/config/helper' import { Config } from 'utils/config/helper'
@ -50,9 +50,9 @@ export function SideBar() {
const configContext = useConfigs() const configContext = useConfigs()
const blockLeaveRef = React.useRef(false) const blockLeaveRef = useRef(false)
const { sidebarToggleMode, shortcut, focusSearchInputShortcut } = configContext.value const { sidebarToggleMode, shortcut, focusSearchInputShortcut } = configContext.value
const onResizeStateChange = React.useCallback((state: ResizeState) => { const onResizeStateChange = useCallback((state: ResizeState) => {
blockLeaveRef.current = state === 'resizing' blockLeaveRef.current = state === 'resizing'
}, []) }, [])
@ -61,7 +61,7 @@ export function SideBar() {
() => useWindowSize().height, // eslint-disable-line react-hooks/rules-of-hooks () => useWindowSize().height, // eslint-disable-line react-hooks/rules-of-hooks
) )
const sidebarContextValue = React.useMemo(() => ({ pendingFocusTarget }), [pendingFocusTarget]) const sidebarContextValue = useMemo(() => ({ pendingFocusTarget }), [pendingFocusTarget])
const placement = configContext.value.sidebarPlacement const placement = configContext.value.sidebarPlacement
@ -146,15 +146,12 @@ export function SideBar() {
</div> </div>
<IIFC> <IIFC>
{() => { {() => {
const [showSettings, setShowSettings] = React.useState(false) const [showSettings, setShowSettings] = useState(false)
const toggleShowSettings = React.useCallback( const toggleShowSettings = useCallback(() => setShowSettings(show => !show), [])
() => setShowSettings(show => !show),
[],
)
useOnShortcutPressed( useOnShortcutPressed(
focusSearchInputShortcut, focusSearchInputShortcut,
React.useCallback(() => setShowSettings(false), []), useCallback(() => setShowSettings(false), []),
) )
return ( return (
@ -201,19 +198,19 @@ function ToggleShowButtonWrapper({
} }
function useFocusSidebarOnExpand(shouldExpand: boolean) { function useFocusSidebarOnExpand(shouldExpand: boolean) {
React.useEffect(() => { useEffect(() => {
// prevent keeping focus within Gitako // prevent keeping focus within Gitako
if (!shouldExpand) document.body.focus() if (!shouldExpand) document.body.focus()
}, [shouldExpand]) }, [shouldExpand])
} }
function useMarkGitakoGlobalAttributes() { function useMarkGitakoGlobalAttributes() {
React.useEffect(() => { useEffect(() => {
const detach = DOMHelper.attachStickyGitakoPlatform() const detach = DOMHelper.attachStickyGitakoPlatform()
DOMHelper.markGitakoPlatform() DOMHelper.markGitakoPlatform()
return () => detach() return () => detach()
}, []) }, [])
React.useEffect(() => { useEffect(() => {
const detach = DOMHelper.attachStickyGitakoReadyState() const detach = DOMHelper.attachStickyGitakoReadyState()
DOMHelper.markGitakoReadyState(true) DOMHelper.markGitakoReadyState(true)
return () => { return () => {
@ -224,8 +221,8 @@ function useMarkGitakoGlobalAttributes() {
} }
function useLogoContainerElement() { function useLogoContainerElement() {
const [logoContainerElement, setLogoContainerElement] = React.useState<HTMLElement | null>(null) const [logoContainerElement, setLogoContainerElement] = useState<HTMLElement | null>(null)
React.useEffect(() => { useEffect(() => {
setLogoContainerElement(DOMHelper.insertLogoMountPoint()) setLogoContainerElement(DOMHelper.insertLogoMountPoint())
}, []) }, [])
return logoContainerElement return logoContainerElement
@ -233,7 +230,7 @@ function useLogoContainerElement() {
function useUpdateBodyIndentOnStateUpdate(shouldExpand: boolean) { function useUpdateBodyIndentOnStateUpdate(shouldExpand: boolean) {
const { sidebarToggleMode, sidebarPlacement } = useConfigs().value const { sidebarToggleMode, sidebarPlacement } = useConfigs().value
React.useEffect(() => { useEffect(() => {
if (!(sidebarToggleMode === 'persistent' && shouldExpand)) return if (!(sidebarToggleMode === 'persistent' && shouldExpand)) return
const detach = DOMHelper.attachStickyBodyIndent() const detach = DOMHelper.attachStickyBodyIndent()
@ -257,7 +254,7 @@ const getDerivedExpansion = ({
function useGetDerivedExpansion() { function useGetDerivedExpansion() {
const { intelligentToggle, sidebarToggleMode } = useConfigs().value const { intelligentToggle, sidebarToggleMode } = useConfigs().value
return React.useCallback( return useCallback(
() => getDerivedExpansion({ intelligentToggle, sidebarToggleMode }), () => getDerivedExpansion({ intelligentToggle, sidebarToggleMode }),
[intelligentToggle, sidebarToggleMode], [intelligentToggle, sidebarToggleMode],
) )
@ -266,7 +263,7 @@ function useGetDerivedExpansion() {
function useUpdateBodyIndentAfterRedirect(update: (shouldExpand: boolean) => void) { function useUpdateBodyIndentAfterRedirect(update: (shouldExpand: boolean) => void) {
const { intelligentToggle, sidebarToggleMode, sidebarPlacement } = useConfigs().value const { intelligentToggle, sidebarToggleMode, sidebarPlacement } = useConfigs().value
useAfterRedirect( useAfterRedirect(
React.useCallback(() => { useCallback(() => {
// check and update expand state if pinned and auto-expand checked // check and update expand state if pinned and auto-expand checked
if (sidebarToggleMode === 'persistent') { if (sidebarToggleMode === 'persistent') {
const shouldExpand = getDerivedExpansion({ intelligentToggle, sidebarToggleMode }) const shouldExpand = getDerivedExpansion({ intelligentToggle, sidebarToggleMode })
@ -282,7 +279,7 @@ function useUpdateBodyIndentAfterRedirect(update: (shouldExpand: boolean) => voi
function useSaveExpandStateOnToggle(shouldExpand: boolean) { function useSaveExpandStateOnToggle(shouldExpand: boolean) {
const configContext = useConfigs() const configContext = useConfigs()
const { intelligentToggle } = configContext.value const { intelligentToggle } = configContext.value
React.useEffect(() => { useEffect(() => {
if (intelligentToggle !== null) configContext.onChange({ intelligentToggle: shouldExpand }) if (intelligentToggle !== null) configContext.onChange({ intelligentToggle: shouldExpand })
}, [shouldExpand, intelligentToggle]) // eslint-disable-line react-hooks/exhaustive-deps }, [shouldExpand, intelligentToggle]) // eslint-disable-line react-hooks/exhaustive-deps
} }
@ -297,7 +294,7 @@ function useCollapseOnNoPermissionWhenTokenHasBeenSet(
intelligentToggle === null && intelligentToggle === null &&
!!accessToken && !!accessToken &&
state === 'error-due-to-auth' state === 'error-due-to-auth'
React.useEffect(() => { useEffect(() => {
if (hideSidebarOnInvalidToken) setShowSideBar(false) if (hideSidebarOnInvalidToken) setShowSideBar(false)
}, [hideSidebarOnInvalidToken, setShowSideBar]) }, [hideSidebarOnInvalidToken, setShowSideBar])
} }
@ -305,11 +302,8 @@ function useCollapseOnNoPermissionWhenTokenHasBeenSet(
function useShouldExpand() { function useShouldExpand() {
const getDerivedExpansion = useGetDerivedExpansion() const getDerivedExpansion = useGetDerivedExpansion()
const error = useLoadedContext(SideBarErrorContext).value const error = useLoadedContext(SideBarErrorContext).value
const [shouldExpand, setShouldExpand] = React.useState(getDerivedExpansion) const [shouldExpand, setShouldExpand] = useState(getDerivedExpansion)
const toggleShowSideBar = React.useCallback( const toggleShowSideBar = useCallback(() => setShouldExpand(show => !show), [setShouldExpand])
() => setShouldExpand(show => !show),
[setShouldExpand],
)
const $shouldExpand = error ? false : shouldExpand const $shouldExpand = error ? false : shouldExpand
@ -331,7 +325,7 @@ function useShowSidebarKeyboard(
useOnShortcutPressed( useOnShortcutPressed(
config.shortcut, config.shortcut,
React.useCallback( useCallback(
e => { e => {
DOMHelper.cancelEvent(e) DOMHelper.cancelEvent(e)
toggleShowSideBar() toggleShowSideBar()
@ -343,7 +337,7 @@ function useShowSidebarKeyboard(
useOnShortcutPressed( useOnShortcutPressed(
config.focusSearchInputShortcut, config.focusSearchInputShortcut,
React.useCallback( useCallback(
e => { e => {
DOMHelper.cancelEvent(e) DOMHelper.cancelEvent(e)
if (!shouldExpand) setShouldExpand(true) if (!shouldExpand) setShouldExpand(true)

View file

@ -1,5 +1,5 @@
import { useConfigs } from 'containers/ConfigsContext' import { useConfigs } from 'containers/ConfigsContext'
import * as React from 'react' import React, { useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react'
import { useDebounce, useLatest, useWindowSize } from 'react-use' import { useDebounce, useLatest, useWindowSize } from 'react-use'
import { getDefaultConfigs } from 'utils/config/helper' import { getDefaultConfigs } from 'utils/config/helper'
import * as DOMHelper from 'utils/DOMHelper' import * as DOMHelper from 'utils/DOMHelper'
@ -18,24 +18,24 @@ function useSidebarWidth() {
// width => --gitako-width // layout effect // width => --gitako-width // layout effect
// resize event => width // resize event => width
// resize event => --gitako-width // rAF // resize event => --gitako-width // rAF
const [width, setWidth] = React.useState(configContext.value.sideBarWidth) const [width, setWidth] = useState(configContext.value.sideBarWidth)
const { width: windowWidth } = useWindowSize() const { width: windowWidth } = useWindowSize()
React.useEffect(() => { useEffect(() => {
const safeSize = getSafeWidth(width, windowWidth) const safeSize = getSafeWidth(width, windowWidth)
if (safeSize !== width) setWidth(safeSize) if (safeSize !== width) setWidth(safeSize)
}, [windowWidth, width]) }, [windowWidth, width])
useDebounce(() => configContext.onChange({ sideBarWidth: width }), 100, [width]) useDebounce(() => configContext.onChange({ sideBarWidth: width }), 100, [width])
React.useLayoutEffect(() => DOMHelper.setGitakoWidthCSSVariable(width), [width]) useLayoutEffect(() => DOMHelper.setGitakoWidthCSSVariable(width), [width])
const widthRef = useLatest(width) const widthRef = useLatest(width)
React.useEffect(() => { useEffect(() => {
const detach = DOMHelper.attachStickyGitakoWidthCSSVariable(() => widthRef.current) const detach = DOMHelper.attachStickyGitakoWidthCSSVariable(() => widthRef.current)
return () => detach() return () => detach()
}, [widthRef]) }, [widthRef])
// Keep variable when directing from PR to repo home via meta bar // Keep variable when directing from PR to repo home via meta bar
useAfterRedirect(React.useCallback(() => DOMHelper.setGitakoWidthCSSVariable(width), [width])) useAfterRedirect(useCallback(() => DOMHelper.setGitakoWidthCSSVariable(width), [width]))
return [width, setWidth] as const return [width, setWidth] as const
} }
@ -45,7 +45,7 @@ export function SideBarResizeHandler({
}: Pick<ResizeHandlerOptions, 'onResizeStateChange'>) { }: Pick<ResizeHandlerOptions, 'onResizeStateChange'>) {
const [width, setWidth] = useSidebarWidth() const [width, setWidth] = useSidebarWidth()
const { width: windowWidth } = useWindowSize() const { width: windowWidth } = useWindowSize()
const onResize = React.useMemo(() => { const onResize = useMemo(() => {
let widthToApply: Size let widthToApply: Size
let pending = false let pending = false
return ([width]: Size2D) => { return ([width]: Size2D) => {
@ -65,12 +65,9 @@ export function SideBarResizeHandler({
} }
}, [windowWidth, setWidth]) }, [windowWidth, setWidth])
const onResetSize = React.useCallback( const onResetSize = useCallback(() => setWidth(getDefaultConfigs().sideBarWidth), [setWidth])
() => setWidth(getDefaultConfigs().sideBarWidth),
[setWidth],
)
const dummySize: Size2D = React.useMemo(() => [width, 0], [width]) const dummySize: Size2D = useMemo(() => [width, 0], [width])
const placement = useConfigs().value.sidebarPlacement const placement = useConfigs().value.sidebarPlacement

View file

@ -3,7 +3,7 @@ import iconURL from 'assets/icons/Gitako.png'
import { useConfigs } from 'containers/ConfigsContext' import { useConfigs } from 'containers/ConfigsContext'
import { SideBarErrorContext } from 'containers/ErrorContext' import { SideBarErrorContext } from 'containers/ErrorContext'
import { ReloadContext } from 'containers/ReloadContext' import { ReloadContext } from 'containers/ReloadContext'
import * as React from 'react' import React, { useContext, useEffect, useLayoutEffect, useRef, useState } from 'react'
import { useDebounce, useWindowSize } from 'react-use' import { useDebounce, useWindowSize } from 'react-use'
import { cx } from 'utils/cx' import { cx } from 'utils/cx'
import { useLoadedContext } from 'utils/hooks/useLoadedContext' import { useLoadedContext } from 'utils/hooks/useLoadedContext'
@ -23,14 +23,14 @@ function getSafeDistance(y: number, height: number) {
} }
export function ToggleShowButton({ className, onClick, onHover }: Props) { export function ToggleShowButton({ className, onClick, onHover }: Props) {
const reload = React.useContext(ReloadContext) const reload = useContext(ReloadContext)
const error = useLoadedContext(SideBarErrorContext).value const error = useLoadedContext(SideBarErrorContext).value
const ref = React.useRef<HTMLDivElement>(null) const ref = useRef<HTMLDivElement>(null)
const config = useConfigs() const config = useConfigs()
const [distance, setDistance] = React.useState(config.value.toggleButtonVerticalDistance) const [distance, setDistance] = useState(config.value.toggleButtonVerticalDistance)
const { height } = useWindowSize() const { height } = useWindowSize()
React.useEffect(() => { useEffect(() => {
// make sure it is inside viewport // make sure it is inside viewport
const safeDistance = getSafeDistance(distance, height) const safeDistance = getSafeDistance(distance, height)
if (safeDistance !== distance) setDistance(safeDistance) if (safeDistance !== distance) setDistance(safeDistance)
@ -44,7 +44,7 @@ export function ToggleShowButton({ className, onClick, onHover }: Props) {
) )
// reposition on window height change, but ignores distance change // reposition on window height change, but ignores distance change
React.useLayoutEffect(() => { useLayoutEffect(() => {
if (ref.current) { if (ref.current) {
ref.current.style.top = distance + 'px' ref.current.style.top = distance + 'px'
} }

View file

@ -1,4 +1,4 @@
import * as React from 'react' import React from 'react'
import { cx } from 'utils/cx' import { cx } from 'utils/cx'
import { hasUpperCase } from 'utils/general' import { hasUpperCase } from 'utils/general'
import { ModeShape } from '.' import { ModeShape } from '.'

View file

@ -1,4 +1,4 @@
import * as React from 'react' import React from 'react'
import { cx } from 'utils/cx' import { cx } from 'utils/cx'
import { searchKeyToRegexp } from 'utils/general' import { searchKeyToRegexp } from 'utils/general'
import { ModeShape } from '.' import { ModeShape } from '.'

View file

@ -5,7 +5,7 @@ import { SideBarStateContext } from 'containers/SideBarState'
import { platform } from 'platforms' import { platform } from 'platforms'
import { Gitea } from 'platforms/Gitea' import { Gitea } from 'platforms/Gitea'
import { Gitee } from 'platforms/Gitee' import { Gitee } from 'platforms/Gitee'
import * as React from 'react' import React, { useCallback, useEffect, useState } from 'react'
import { IIFC } from 'react-iifc' import { IIFC } from 'react-iifc'
import { useLoadedContext } from 'utils/hooks/useLoadedContext' import { useLoadedContext } from 'utils/hooks/useLoadedContext'
import { useStateIO } from 'utils/hooks/useStateIO' import { useStateIO } from 'utils/hooks/useStateIO'
@ -17,19 +17,19 @@ export function AccessTokenSettings() {
const configContext = useConfigs() const configContext = useConfigs()
const { accessToken } = configContext.value const { accessToken } = configContext.value
const hasAccessToken = Boolean(accessToken) const hasAccessToken = Boolean(accessToken)
const [accessTokenInputValue, setAccessTokenInputValue] = React.useState('') const [accessTokenInputValue, setAccessTokenInputValue] = useState('')
const useAccessTokenHint = useStateIO<React.ReactNode>('') const useAccessTokenHint = useStateIO<React.ReactNode>('')
const focusInput = useStateIO(false) const focusInput = useStateIO(false)
const sidebarState = useLoadedContext(SideBarStateContext).value const sidebarState = useLoadedContext(SideBarStateContext).value
const { value: accessTokenHint } = useAccessTokenHint const { value: accessTokenHint } = useAccessTokenHint
React.useEffect(() => { useEffect(() => {
// clear input when access token updates // clear input when access token updates
setAccessTokenInputValue('') setAccessTokenInputValue('')
}, [accessToken]) }, [accessToken])
const saveToken = React.useCallback( const saveToken = useCallback(
async ( async (
hint: typeof useAccessTokenHint.value = ( hint: typeof useAccessTokenHint.value = (
<span> <span>
@ -73,16 +73,16 @@ export function AccessTokenSettings() {
) : hasAccessToken ? ( ) : hasAccessToken ? (
<IIFC> <IIFC>
{() => { {() => {
const [showConfirmButton, setShowConfirmButton] = React.useState(false) const [showConfirmButton, setShowConfirmButton] = useState(false)
return ( return (
<Box> <Box>
{showConfirmButton ? ( {showConfirmButton ? (
<IIFC> <IIFC>
{() => { {() => {
const [allowClear, setAllowClear] = React.useState(false) const [allowClear, setAllowClear] = useState(false)
const waitForSeconds = 3 const waitForSeconds = 3
const timePast = useTimePast(1000, waitForSeconds * 1000) const timePast = useTimePast(1000, waitForSeconds * 1000)
React.useEffect(() => { useEffect(() => {
const timeout = setTimeout(() => setAllowClear(true), waitForSeconds * 1000) const timeout = setTimeout(() => setAllowClear(true), waitForSeconds * 1000)
return () => clearTimeout(timeout) return () => clearTimeout(timeout)
}, []) }, [])
@ -168,8 +168,8 @@ export function AccessTokenSettings() {
} }
function useTimePast(unit = 1000, max?: number) { function useTimePast(unit = 1000, max?: number) {
const [timePast, setTimePast] = React.useState(0) const [timePast, setTimePast] = useState(0)
React.useEffect(() => { useEffect(() => {
const checkInterval = (unit / 10) >> 0 // 10x check times for better accuracy const checkInterval = (unit / 10) >> 0 // 10x check times for better accuracy
const start = Date.now() const start = Date.now()
let memoLastValue = 0 let memoLastValue = 0

View file

@ -1,6 +1,6 @@
import { wikiLinks } from 'components/settings/SettingsBar' import { wikiLinks } from 'components/settings/SettingsBar'
import { SimpleConfigFieldCheckbox } from 'components/settings/SimpleConfigField/Checkbox' import { SimpleConfigFieldCheckbox } from 'components/settings/SimpleConfigField/Checkbox'
import * as React from 'react' import React from 'react'
import { Config } from 'utils/config/helper' import { Config } from 'utils/config/helper'
import { Option } from '../Inputs/SelectInput' import { Option } from '../Inputs/SelectInput'
import { SettingsSection } from './SettingsSection' import { SettingsSection } from './SettingsSection'

View file

@ -1,5 +1,5 @@
import { Box, Button, FormControl, TextInput } from '@primer/react' import { Box, Button, FormControl, TextInput } from '@primer/react'
import * as React from 'react' import React, { useMemo } from 'react'
import { useUpdateEffect } from 'react-use' import { useUpdateEffect } from 'react-use'
import { cancelEvent } from 'utils/DOMHelper' import { cancelEvent } from 'utils/DOMHelper'
import { friendlyFormatShortcut, noop } from 'utils/general' import { friendlyFormatShortcut, noop } from 'utils/general'
@ -15,7 +15,7 @@ export function KeyboardShortcutSetting({ label, value, onChange }: Props) {
const $shortcut = useStateIO(value) const $shortcut = useStateIO(value)
useUpdateEffect(() => $shortcut.onChange(value), [value]) useUpdateEffect(() => $shortcut.onChange(value), [value])
const id = React.useMemo(() => Math.random() + '', []) const id = useMemo(() => Math.random() + '', [])
return ( return (
<FormControl> <FormControl>

View file

@ -5,7 +5,7 @@ import { RoundIconButton } from 'components/RoundIconButton'
import { useConfigs } from 'containers/ConfigsContext' import { useConfigs } from 'containers/ConfigsContext'
import { platform } from 'platforms' import { platform } from 'platforms'
import { GitHub } from 'platforms/GitHub' import { GitHub } from 'platforms/GitHub'
import * as React from 'react' import React from 'react'
import { useUpdateEffect } from 'react-use' import { useUpdateEffect } from 'react-use'
import { useStateIO } from 'utils/hooks/useStateIO' import { useStateIO } from 'utils/hooks/useStateIO'
import { AccessTokenSettings } from './AccessTokenSettings' import { AccessTokenSettings } from './AccessTokenSettings'

View file

@ -1,5 +1,5 @@
import { Box } from '@primer/react' import { Box } from '@primer/react'
import * as React from 'react' import React from 'react'
type Props = { type Props = {
title?: React.ReactNode title?: React.ReactNode

View file

@ -1,6 +1,6 @@
import { SimpleConfigFieldCheckbox } from 'components/settings/SimpleConfigField/Checkbox' import { SimpleConfigFieldCheckbox } from 'components/settings/SimpleConfigField/Checkbox'
import { useConfigs } from 'containers/ConfigsContext' import { useConfigs } from 'containers/ConfigsContext'
import * as React from 'react' import React from 'react'
import { subIO } from 'utils/general' import { subIO } from 'utils/general'
import { KeyboardShortcutSetting } from './KeyboardShortcutSetting' import { KeyboardShortcutSetting } from './KeyboardShortcutSetting'
import { SettingsSection } from './SettingsSection' import { SettingsSection } from './SettingsSection'

View file

@ -1,4 +1,4 @@
import * as React from 'react' import React from 'react'
import { ConfigKeys } from 'utils/config/helper' import { ConfigKeys } from 'utils/config/helper'
import { SimpleConfigFieldProps, useSimpleConfigFieldIO } from '.' import { SimpleConfigFieldProps, useSimpleConfigFieldIO } from '.'
import { Checkbox } from '../../Inputs/Checkbox' import { Checkbox } from '../../Inputs/Checkbox'

View file

@ -1,6 +1,6 @@
import { InfoIcon, LinkExternalIcon } from '@primer/octicons-react' import { InfoIcon, LinkExternalIcon } from '@primer/octicons-react'
import { Box } from '@primer/react' import { Box } from '@primer/react'
import * as React from 'react' import React from 'react'
import { ConfigKeys } from 'utils/config/helper' import { ConfigKeys } from 'utils/config/helper'
import { SimpleConfigField } from '.' import { SimpleConfigField } from '.'

View file

@ -1,5 +1,5 @@
import { SelectInput, SelectInputProps } from 'components/Inputs/SelectInput' import { SelectInput, SelectInputProps } from 'components/Inputs/SelectInput'
import * as React from 'react' import React from 'react'
import { Config, ConfigKeys } from 'utils/config/helper' import { Config, ConfigKeys } from 'utils/config/helper'
import { SimpleConfigFieldProps, useSimpleConfigFieldIO } from '.' import { SimpleConfigFieldProps, useSimpleConfigFieldIO } from '.'
import { FieldLabel } from './FieldLabel' import { FieldLabel } from './FieldLabel'

View file

@ -1,5 +1,5 @@
import { useConfigs } from 'containers/ConfigsContext' import { useConfigs } from 'containers/ConfigsContext'
import React from 'react' import { useCallback, useMemo } from 'react'
import { Config, ConfigKeys } from 'utils/config/helper' import { Config, ConfigKeys } from 'utils/config/helper'
export type SimpleConfigField<Key extends ConfigKeys> = { export type SimpleConfigField<Key extends ConfigKeys> = {
@ -26,8 +26,8 @@ export function useSimpleConfigFieldIO<Key extends ConfigKeys>(
const value = configContext.value[field.key] const value = configContext.value[field.key]
return { return {
value: React.useMemo(() => (overwrite ? overwrite.value(value) : value), [overwrite, value]), value: useMemo(() => (overwrite ? overwrite.value(value) : value), [overwrite, value]),
onChange: React.useCallback( onChange: useCallback(
(newValue: Config[Key]) => { (newValue: Config[Key]) => {
configContext.onChange({ [field.key]: overwrite ? overwrite.onChange(newValue) : newValue }) configContext.onChange({ [field.key]: overwrite ? overwrite.onChange(newValue) : newValue })
}, },

View file

@ -1,5 +1,5 @@
import { PropsWithChildren } from 'common' import { PropsWithChildren } from 'common'
import * as React from 'react' import React, { useCallback, useContext, useEffect, useState } from 'react'
import { Config, configHelper } from 'utils/config/helper' import { Config, configHelper } from 'utils/config/helper'
type ContextShape = IO<Config, Partial<Config>> type ContextShape = IO<Config, Partial<Config>>
@ -8,11 +8,11 @@ export type ConfigsContextShape = ContextShape
export const ConfigsContext = React.createContext<ContextShape | null>(null) export const ConfigsContext = React.createContext<ContextShape | null>(null)
export function ConfigsContextWrapper(props: PropsWithChildren) { export function ConfigsContextWrapper(props: PropsWithChildren) {
const [configs, setConfigs] = React.useState<Config | null>(null) const [configs, setConfigs] = useState<Config | null>(null)
React.useEffect(() => { useEffect(() => {
configHelper.get().then(setConfigs) configHelper.get().then(setConfigs)
}, []) }, [])
const onChange = React.useCallback( const onChange = useCallback(
(updatedConfigs: Partial<Config>) => { (updatedConfigs: Partial<Config>) => {
const mergedConfigs = { ...configs, ...updatedConfigs } as Config const mergedConfigs = { ...configs, ...updatedConfigs } as Config
configHelper.set(mergedConfigs) configHelper.set(mergedConfigs)
@ -32,7 +32,7 @@ export const useConfigs = createUseNonNullContext(ConfigsContext)
function createUseNonNullContext<T>(theContext: React.Context<T | null>): () => T { function createUseNonNullContext<T>(theContext: React.Context<T | null>): () => T {
return () => { return () => {
const context = React.useContext(theContext) const context = useContext(theContext)
if (context === null) throw new Error(`Empty context`) if (context === null) throw new Error(`Empty context`)
return context return context
} }

View file

@ -1,6 +1,6 @@
import { raiseError } from 'analytics' import { raiseError } from 'analytics'
import { PropsWithChildren } from 'common' import { PropsWithChildren } from 'common'
import * as React from 'react' import React from 'react'
export class ErrorBoundary extends React.PureComponent<PropsWithChildren> { export class ErrorBoundary extends React.PureComponent<PropsWithChildren> {
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {

View file

@ -1,6 +1,6 @@
import { PropsWithChildren } from 'common' import { PropsWithChildren } from 'common'
import { useInspector } from 'containers/Inspector' import { useInspector } from 'containers/Inspector'
import * as React from 'react' import React from 'react'
import { useStateIO } from 'utils/hooks/useStateIO' import { useStateIO } from 'utils/hooks/useStateIO'
export type SideBarErrorContextShape = IO<string | null> export type SideBarErrorContextShape = IO<string | null>

View file

@ -1,10 +1,10 @@
import { PropsWithChildren, ReactIO } from 'common' import { PropsWithChildren, ReactIO } from 'common'
import { IN_PRODUCTION_MODE } from 'env' import { IN_PRODUCTION_MODE } from 'env'
import * as React from 'react' import React, { useContext, useEffect } from 'react'
import { Config } from 'utils/config/helper'
import { noop } from 'utils/general' import { noop } from 'utils/general'
import { useStateIO } from 'utils/hooks/useStateIO' import { useStateIO } from 'utils/hooks/useStateIO'
import { useConfigs } from './ConfigsContext' import { useConfigs } from './ConfigsContext'
import { Config } from 'utils/config/helper'
export type InspectorContextShape = ReactIO<JSONObject> export type InspectorContextShape = ReactIO<JSONObject>
@ -66,8 +66,8 @@ export const InspectorContextWrapper = IN_PRODUCTION_MODE
export const useInspector = IN_PRODUCTION_MODE export const useInspector = IN_PRODUCTION_MODE
? noop ? noop
: function useInspector(key: string, value: JSONValue) { : function useInspector(key: string, value: JSONValue) {
const $ = React.useContext(InspectorContext) const $ = useContext(InspectorContext)
React.useEffect(() => { useEffect(() => {
$?.onChange(prev => ({ ...prev, [key]: value })) $?.onChange(prev => ({ ...prev, [key]: value }))
}, [key, value]) // eslint-disable-line react-hooks/exhaustive-deps }, [key, value]) // eslint-disable-line react-hooks/exhaustive-deps
} }

View file

@ -1,7 +1,7 @@
import { PropsWithChildren } from 'common' import { PropsWithChildren } from 'common'
import { useConfigs } from 'containers/ConfigsContext' import { useConfigs } from 'containers/ConfigsContext'
import { platform } from 'platforms' import { platform } from 'platforms'
import * as React from 'react' import React, { useEffect, useRef } from 'react'
import { parseURLSearch, run } from 'utils/general' import { parseURLSearch, run } from 'utils/general'
import { useLoadedContext } from 'utils/hooks/useLoadedContext' import { useLoadedContext } from 'utils/hooks/useLoadedContext'
import { useStateIO } from 'utils/hooks/useStateIO' import { useStateIO } from 'utils/hooks/useStateIO'
@ -15,8 +15,8 @@ export function OAuthWrapper({ children }: PropsWithChildren) {
const running = useGetAccessToken() const running = useGetAccessToken()
const $state = useLoadedContext(SideBarStateContext) const $state = useLoadedContext(SideBarStateContext)
const needGetAccessTokenRef = React.useRef(running) const needGetAccessTokenRef = useRef(running)
React.useEffect(() => { useEffect(() => {
if (needGetAccessTokenRef.current) { if (needGetAccessTokenRef.current) {
$state.onChange(running ? 'getting-access-token' : 'after-getting-access-token') $state.onChange(running ? 'getting-access-token' : 'after-getting-access-token')
} }
@ -31,7 +31,7 @@ function useGetAccessToken() {
const $block = useStateIO(() => Boolean(getCodeSearchParam())) const $block = useStateIO(() => Boolean(getCodeSearchParam()))
const configContext = useConfigs() const configContext = useConfigs()
const { accessToken } = configContext.value const { accessToken } = configContext.value
React.useEffect(() => { useEffect(() => {
run(async function () { run(async function () {
const code = getCodeSearchParam() const code = getCodeSearchParam()
if (code && !accessToken) { if (code && !accessToken) {

View file

@ -1,4 +1,4 @@
import * as React from 'react' import React from 'react'
export type PortalContextShape = string | null export type PortalContextShape = string | null

View file

@ -1,5 +1,5 @@
import { PropsWithChildren } from 'common' import { PropsWithChildren } from 'common'
import * as React from 'react' import React, { useCallback, useState } from 'react'
import { noop } from 'utils/general' import { noop } from 'utils/general'
export type ReloadContextShape = () => void export type ReloadContextShape = () => void
@ -7,8 +7,8 @@ export type ReloadContextShape = () => void
export const ReloadContext = React.createContext<ReloadContextShape>(noop) export const ReloadContext = React.createContext<ReloadContextShape>(noop)
export function ReloadContextWrapper({ children }: PropsWithChildren) { export function ReloadContextWrapper({ children }: PropsWithChildren) {
const [key, setKey] = React.useState(0) const [key, setKey] = useState(0)
const reload = React.useCallback(() => setKey(key => key + 1), []) const reload = useCallback(() => setKey(key => key + 1), [])
return ( return (
<ReloadContext.Provider key={key} value={reload}> <ReloadContext.Provider key={key} value={reload}>

View file

@ -1,15 +1,15 @@
import { PropsWithChildren } from 'common' import { PropsWithChildren } from 'common'
import { useConfigs } from 'containers/ConfigsContext' import { useConfigs } from 'containers/ConfigsContext'
import { platform } from 'platforms' import { platform } from 'platforms'
import * as React from 'react' import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { useAbortableEffect } from 'utils/hooks/useAbortableEffect' import { useAbortableEffect } from 'utils/hooks/useAbortableEffect'
import { useEffectOnSerializableUpdates } from 'utils/hooks/useEffectOnSerializableUpdates' import { useEffectOnSerializableUpdates } from 'utils/hooks/useEffectOnSerializableUpdates'
import { useAfterRedirect } from 'utils/hooks/useFastRedirect' import { useAfterRedirect } from 'utils/hooks/useFastRedirect'
import { useHandleNetworkError } from 'utils/hooks/useHandleNetworkError' import { useHandleNetworkError } from 'utils/hooks/useHandleNetworkError'
import { useLoadedContext } from 'utils/hooks/useLoadedContext' import { useLoadedContext } from 'utils/hooks/useLoadedContext'
import { useStateIO } from 'utils/hooks/useStateIO' import { useStateIO } from 'utils/hooks/useStateIO'
import { SideBarStateContext } from './SideBarState'
import { useInspector } from './Inspector' import { useInspector } from './Inspector'
import { SideBarStateContext } from './SideBarState'
export const RepoContext = React.createContext<MetaData | null>(null) export const RepoContext = React.createContext<MetaData | null>(null)
@ -18,7 +18,7 @@ export function RepoContextWrapper({ children }: PropsWithChildren) {
const metaData = useMetaData(partialMetaData) const metaData = useMetaData(partialMetaData)
useInspector( useInspector(
'RepoContext', 'RepoContext',
React.useMemo( useMemo(
() => ({ () => ({
partialMetaData, partialMetaData,
metaData, metaData,
@ -54,11 +54,11 @@ function usePartialMetaData(): PartialMetaData | null {
// sync along URL and DOM // sync along URL and DOM
const $partialMetaData = useStateIO(isGettingAccessToken ? null : resolvePartialMetaData) const $partialMetaData = useStateIO(isGettingAccessToken ? null : resolvePartialMetaData)
const $committedPartialMetaData = useStateIO($partialMetaData.value) const $committedPartialMetaData = useStateIO($partialMetaData.value)
const setPartialMetaData = React.useCallback( const setPartialMetaData = useCallback(
() => $partialMetaData.onChange(resolvePartialMetaData()), () => $partialMetaData.onChange(resolvePartialMetaData()),
[], // eslint-disable-line react-hooks/exhaustive-deps [], // eslint-disable-line react-hooks/exhaustive-deps
) )
React.useEffect(() => { useEffect(() => {
if (!isGettingAccessToken) setPartialMetaData() if (!isGettingAccessToken) setPartialMetaData()
}, [isGettingAccessToken, setPartialMetaData]) }, [isGettingAccessToken, setPartialMetaData])
useAfterRedirect(setPartialMetaData) useAfterRedirect(setPartialMetaData)
@ -67,7 +67,7 @@ function usePartialMetaData(): PartialMetaData | null {
JSON.stringify, JSON.stringify,
$committedPartialMetaData.onChange, $committedPartialMetaData.onChange,
) )
React.useEffect(() => { useEffect(() => {
if (!$partialMetaData.value && !isGettingAccessToken) { if (!$partialMetaData.value && !isGettingAccessToken) {
$state.onChange('disabled') $state.onChange('disabled')
} }
@ -76,12 +76,12 @@ function usePartialMetaData(): PartialMetaData | null {
} }
function useMetaData(partialMetaData: PartialMetaData | null) { function useMetaData(partialMetaData: PartialMetaData | null) {
const [metaData, changeMetaData] = React.useState<MetaData | null>(null) const [metaData, changeMetaData] = useState<MetaData | null>(null)
const changeLoadedState = useLoadedContext(SideBarStateContext).onChange const changeLoadedState = useLoadedContext(SideBarStateContext).onChange
const handleNetworkError = useHandleNetworkError() const handleNetworkError = useHandleNetworkError()
const { accessToken } = useConfigs().value const { accessToken } = useConfigs().value
const loadRepoMetaData = React.useCallback( const loadRepoMetaData = useCallback(
async function* loadRepoMetaData() { async function* loadRepoMetaData() {
if (!partialMetaData) return if (!partialMetaData) return
@ -115,7 +115,7 @@ function useMetaData(partialMetaData: PartialMetaData | null) {
) )
useAbortableEffect( useAbortableEffect(
React.useCallback( useCallback(
() => ({ () => ({
getAsyncGenerator: loadRepoMetaData, getAsyncGenerator: loadRepoMetaData,
cancel: () => { cancel: () => {

View file

@ -1,5 +1,5 @@
import { PropsWithChildren } from 'common' import { PropsWithChildren } from 'common'
import * as React from 'react' import React, { useMemo } from 'react'
import { useLoadedContext } from 'utils/hooks/useLoadedContext' import { useLoadedContext } from 'utils/hooks/useLoadedContext'
import { useStateIO } from 'utils/hooks/useStateIO' import { useStateIO } from 'utils/hooks/useStateIO'
import { SideBarErrorContext } from './ErrorContext' import { SideBarErrorContext } from './ErrorContext'
@ -26,7 +26,7 @@ export function StateBarStateContextWrapper({ children }: PropsWithChildren) {
const $state = useStateIO<SideBarState>('disabled') const $state = useStateIO<SideBarState>('disabled')
useInspector('SideBarStateContext', $state.value) useInspector('SideBarStateContext', $state.value)
const error = useLoadedContext(SideBarErrorContext).value const error = useLoadedContext(SideBarErrorContext).value
const $$state: IO<SideBarState> = React.useMemo( const $$state: IO<SideBarState> = useMemo(
() => () =>
error && $state.value !== 'error' error && $state.value !== 'error'
? { ? {

View file

@ -2,7 +2,7 @@ import primitives from '@primer/primitives'
import { BaseStyles, ThemeProvider } from '@primer/react' import { BaseStyles, ThemeProvider } from '@primer/react'
import theme from '@primer/react/lib-esm/theme' import theme from '@primer/react/lib-esm/theme'
import { PropsWithChildren } from 'common' import { PropsWithChildren } from 'common'
import * as React from 'react' import React, { useEffect, useState } from 'react'
// Temporary color fix for out-of-date embedded @primer/primitives in @primer/react // Temporary color fix for out-of-date embedded @primer/primitives in @primer/react
// The `*_tritanopia` themes are actually not bundled within @primer/react@35.2.2 // The `*_tritanopia` themes are actually not bundled within @primer/react@35.2.2
@ -17,7 +17,7 @@ const fixedTheme = {
} }
const validColorSchemes = Object.keys(fixedTheme.colorSchemes) as EnumString< const validColorSchemes = Object.keys(fixedTheme.colorSchemes) as EnumString<
keyof typeof primitives['colors'] keyof (typeof primitives)['colors']
>[] >[]
const colorModeMap: Record<string, 'night' | 'day'> = { const colorModeMap: Record<string, 'night' | 'day'> = {
@ -39,8 +39,8 @@ const getPreferenceFromDOM = () => {
} }
function useThemePreference() { function useThemePreference() {
const [prefer, setPrefer] = React.useState(getPreferenceFromDOM) const [prefer, setPrefer] = useState(getPreferenceFromDOM)
React.useEffect(() => { useEffect(() => {
const match = window.matchMedia('(prefers-color-scheme: dark)') const match = window.matchMedia('(prefers-color-scheme: dark)')
const update = () => setPrefer(getPreferenceFromDOM) const update = () => setPrefer(getPreferenceFromDOM)
match.addEventListener('change', update) match.addEventListener('change', update)

View file

@ -1,5 +1,5 @@
import { Gitako } from 'components/Gitako' import { Gitako } from 'components/Gitako'
import * as React from 'react' import React, { useCallback } from 'react'
import { createRoot } from 'react-dom/client' import { createRoot } from 'react-dom/client'
import { insertMountPoint, insertSideBarMountPoint } from 'utils/DOMHelper' import { insertMountPoint, insertSideBarMountPoint } from 'utils/DOMHelper'
import { useAfterRedirect } from 'utils/hooks/useFastRedirect' import { useAfterRedirect } from 'utils/hooks/useFastRedirect'
@ -15,7 +15,7 @@ async function init() {
await injectStyles(browser.runtime.getURL('content.css')) await injectStyles(browser.runtime.getURL('content.css'))
const mountPoint = insertSideBarMountPoint() const mountPoint = insertSideBarMountPoint()
const MountPointWatcher = () => { const MountPointWatcher = () => {
useAfterRedirect(React.useCallback(() => insertMountPoint(() => mountPoint), [])) useAfterRedirect(useCallback(() => insertMountPoint(() => mountPoint), []))
return null return null
} }

View file

@ -1,4 +1,4 @@
import * as React from 'react' import React, { useEffect, useRef, useState } from 'react'
import { cx } from 'utils/cx' import { cx } from 'utils/cx'
import { copyElementContent } from 'utils/DOMHelper' import { copyElementContent } from 'utils/DOMHelper'
import { getCodeElement } from './DOMHelper' import { getCodeElement } from './DOMHelper'
@ -13,8 +13,8 @@ const contents = {
} }
export function CopyFileButton() { export function CopyFileButton() {
const [content, setContent] = React.useState(contents.normal) const [content, setContent] = useState(contents.normal)
React.useEffect(() => { useEffect(() => {
if (content !== contents.normal) { if (content !== contents.normal) {
const timer = setTimeout(() => { const timer = setTimeout(() => {
setContent(contents.normal) setContent(contents.normal)
@ -23,8 +23,8 @@ export function CopyFileButton() {
} }
}, [content]) }, [content])
const elementRef = React.useRef<HTMLAnchorElement | null>(null) const elementRef = useRef<HTMLAnchorElement | null>(null)
React.useEffect(() => { useEffect(() => {
// Temporary fix: // Temporary fix:
// React moved root node of event delegation since v17 // React moved root node of event delegation since v17
// onClick on <a /> won't work when rendered with `renderReact` // onClick on <a /> won't work when rendered with `renderReact`

View file

@ -1,6 +1,6 @@
import { raiseError } from 'analytics' import { raiseError } from 'analytics'
import { Clippy, ClippyClassName } from 'components/Clippy' import { Clippy, ClippyClassName } from 'components/Clippy'
import * as React from 'react' import React from 'react'
import * as s from 'superstruct' import * as s from 'superstruct'
import { $ } from 'utils/$' import { $ } from 'utils/$'
import { formatClass, parseIntFromElement } from 'utils/DOMHelper' import { formatClass, parseIntFromElement } from 'utils/DOMHelper'

View file

@ -1,16 +1,16 @@
import { platform } from 'platforms' import { platform } from 'platforms'
import * as React from 'react' import { useCallback, useEffect } from 'react'
import { useAfterRedirect } from 'utils/hooks/useFastRedirect' import { useAfterRedirect } from 'utils/hooks/useFastRedirect'
import * as DOMHelper from '../DOMHelper' import * as DOMHelper from '../DOMHelper'
import { GitHub } from '../index' import { GitHub } from '../index'
export function useGitHubAttachCopySnippetButton(copySnippetButton: boolean) { export function useGitHubAttachCopySnippetButton(copySnippetButton: boolean) {
const attachCopySnippetButton = React.useCallback( const attachCopySnippetButton = useCallback(
function attachCopySnippetButton() { function attachCopySnippetButton() {
if (platform === GitHub && copySnippetButton) DOMHelper.attachCopySnippet() if (platform === GitHub && copySnippetButton) DOMHelper.attachCopySnippet()
}, },
[copySnippetButton], [copySnippetButton],
) )
React.useEffect(attachCopySnippetButton, [attachCopySnippetButton]) useEffect(attachCopySnippetButton, [attachCopySnippetButton])
useAfterRedirect(attachCopySnippetButton) useAfterRedirect(attachCopySnippetButton)
} }

View file

@ -1,6 +1,6 @@
import { raiseError } from 'analytics' import { raiseError } from 'analytics'
import { Clippy, ClippyClassName } from 'components/Clippy' import { Clippy, ClippyClassName } from 'components/Clippy'
import * as React from 'react' import React from 'react'
import { $ } from 'utils/$' import { $ } from 'utils/$'
import { formatClass } from 'utils/DOMHelper' import { formatClass } from 'utils/DOMHelper'
import { renderReact } from 'utils/general' import { renderReact } from 'utils/general'

View file

@ -1,7 +1,7 @@
import { GITEE_OAUTH } from 'env' import { GITEE_OAUTH } from 'env'
import { Base64 } from 'js-base64' import { Base64 } from 'js-base64'
import { errors, platform } from 'platforms' import { errors, platform } from 'platforms'
import * as React from 'react' import { useCallback, useEffect } from 'react'
import { resolveGitModules } from 'utils/gitSubmodule' import { resolveGitModules } from 'utils/gitSubmodule'
import { useAfterRedirect } from 'utils/hooks/useFastRedirect' import { useAfterRedirect } from 'utils/hooks/useFastRedirect'
import { useProgressBar } from 'utils/hooks/useProgressBar' import { useProgressBar } from 'utils/hooks/useProgressBar'
@ -187,12 +187,12 @@ export const Gitee: Platform = {
} }
export function useGiteeAttachCopySnippetButton(copySnippetButton: boolean) { export function useGiteeAttachCopySnippetButton(copySnippetButton: boolean) {
const attachCopySnippetButton = React.useCallback( const attachCopySnippetButton = useCallback(
function attachCopySnippetButton() { function attachCopySnippetButton() {
if (platform === Gitee && copySnippetButton) DOMHelper.attachCopySnippet() if (platform === Gitee && copySnippetButton) DOMHelper.attachCopySnippet()
}, },
[copySnippetButton], [copySnippetButton],
) )
React.useEffect(attachCopySnippetButton, [attachCopySnippetButton]) useEffect(attachCopySnippetButton, [attachCopySnippetButton])
useAfterRedirect(attachCopySnippetButton) useAfterRedirect(attachCopySnippetButton)
} }

View file

@ -1,6 +1,6 @@
import * as React from 'react' import { useState } from 'react'
export function useConditionalHook<T>(condition: () => boolean, hook: () => T) { export function useConditionalHook<T>(condition: () => boolean, hook: () => T) {
const [use] = React.useState(condition) const [use] = useState(condition)
if (use) return hook() if (use) return hook()
} }

View file

@ -1,10 +1,10 @@
import * as React from 'react' import { useEffect, useMemo } from 'react'
export function useEffectOnSerializableUpdates<T>( export function useEffectOnSerializableUpdates<T>(
value: T, value: T,
serialize: (value: T) => string, serialize: (value: T) => string,
onChange: (value: T) => void, onChange: (value: T) => void,
) { ) {
const serialized = React.useMemo(() => serialize(value), [value, serialize]) const serialized = useMemo(() => serialize(value), [value, serialize])
React.useEffect(() => onChange(value), [onChange, serialized]) // eslint-disable-line react-hooks/exhaustive-deps useEffect(() => onChange(value), [onChange, serialized]) // eslint-disable-line react-hooks/exhaustive-deps
} }

View file

@ -1,13 +1,13 @@
import * as React from 'react' import { useEffect, useRef, useState } from 'react'
import * as features from 'utils/features' import * as features from 'utils/features'
import { Size2D } from '../../components/Size' import { Size2D } from '../../components/Size'
export function useElementSize<E extends HTMLElement>() { export function useElementSize<E extends HTMLElement>() {
const ref = React.useRef<E | null>(null) const ref = useRef<E | null>(null)
const [size, setSize] = React.useState<Size2D>([0, 0]) const [size, setSize] = useState<Size2D>([0, 0])
React.useEffect(() => { useEffect(() => {
if (ref.current) { if (ref.current) {
if (features.resize) { if (features.resize) {
const observer = new window.ResizeObserver(entries => { const observer = new window.ResizeObserver(entries => {

View file

@ -1,6 +1,6 @@
import { useConfigs } from 'containers/ConfigsContext' import { useConfigs } from 'containers/ConfigsContext'
import { platform } from 'platforms' import { platform } from 'platforms'
import * as React from 'react' import { useCallback, useEffect, useRef } from 'react'
import { useEvent, useInterval } from 'react-use' import { useEvent, useInterval } from 'react-use'
import { run } from 'utils/general' import { run } from 'utils/general'
@ -27,7 +27,7 @@ const config: import('pjax-api').Config = {
export function usePJAXAPI() { export function usePJAXAPI() {
const { pjaxMode } = useConfigs().value const { pjaxMode } = useConfigs().value
// make history travel work // make history travel work
React.useEffect(() => { useEffect(() => {
if (pjaxMode === 'pjax-api') { if (pjaxMode === 'pjax-api') {
run(async () => { run(async () => {
const { Pjax } = await import('pjax-api') const { Pjax } = await import('pjax-api')
@ -52,8 +52,8 @@ export const loadWithFastRedirect = (url: string, element: HTMLElement) => {
} }
export function useAfterRedirect(callback: () => void) { export function useAfterRedirect(callback: () => void) {
const latestHref = React.useRef(location.href) const latestHref = useRef(location.href)
const raceCallback = React.useCallback(() => { const raceCallback = useCallback(() => {
const { href } = location const { href } = location
if (latestHref.current !== href) { if (latestHref.current !== href) {
latestHref.current = href latestHref.current = href

View file

@ -1,7 +1,7 @@
import * as React from 'react' import React, { useContext } from 'react'
export function useLoadedContext<T>(context: React.Context<T | null>): T { export function useLoadedContext<T>(context: React.Context<T | null>): T {
const ctx = React.useContext(context) const ctx = useContext(context)
if (ctx === null) throw new Error(`Context not loaded`) if (ctx === null) throw new Error(`Context not loaded`)
return ctx return ctx
} }

View file

@ -1,4 +1,4 @@
import * as React from 'react' import React, { useEffect } from 'react'
import { useLocation } from 'react-use' import { useLocation } from 'react-use'
export function useOnLocationChange( export function useOnLocationChange(
@ -6,5 +6,5 @@ export function useOnLocationChange(
extraDeps: React.DependencyList = [], extraDeps: React.DependencyList = [],
) { ) {
const { href, pathname, search } = useLocation() const { href, pathname, search } = useLocation()
React.useEffect(callback, [href, pathname, search, callback, ...extraDeps]) useEffect(callback, [href, pathname, search, callback, ...extraDeps])
} }

View file

@ -1,4 +1,4 @@
import * as React from 'react' import { useEffect } from 'react'
import { useLoadedContext } from 'utils/hooks/useLoadedContext' import { useLoadedContext } from 'utils/hooks/useLoadedContext'
import * as keyHelper from 'utils/keyHelper' import * as keyHelper from 'utils/keyHelper'
import { SideBarStateContext } from '../../containers/SideBarState' import { SideBarStateContext } from '../../containers/SideBarState'
@ -9,7 +9,7 @@ export function useOnShortcutPressed(
) { ) {
const state = useLoadedContext(SideBarStateContext).value const state = useLoadedContext(SideBarStateContext).value
const isDisabled = state === 'disabled' || !shortcut const isDisabled = state === 'disabled' || !shortcut
React.useEffect( useEffect(
function attachKeyDown() { function attachKeyDown() {
if (isDisabled) return if (isDisabled) return

View file

@ -1,5 +1,5 @@
import * as NProgress from 'nprogress' import * as NProgress from 'nprogress'
import * as React from 'react' import { useEffect } from 'react'
import { useEvent } from 'react-use' import { useEvent } from 'react-use'
const progressBar = { const progressBar = {
@ -12,7 +12,7 @@ const progressBar = {
} }
export function useProgressBar() { export function useProgressBar() {
React.useEffect(() => { useEffect(() => {
NProgress.configure({ showSpinner: false }) NProgress.configure({ showSpinner: false })
}, []) }, [])
useEvent('pjax:fetch', progressBar.mount, window) useEvent('pjax:fetch', progressBar.mount, window)

View file

@ -1,4 +1,4 @@
import * as React from 'react' import React, { useCallback, useEffect, useRef } from 'react'
import { Size2D } from '../../components/Size' import { Size2D } from '../../components/Size'
export type ResizeState = 'idle' | 'resizing' export type ResizeState = 'idle' | 'resizing'
@ -20,21 +20,21 @@ export function useResizeHandler(
direction = 'right', direction = 'right',
}: ResizeHandlerOptions = {}, }: ResizeHandlerOptions = {},
) { ) {
const pointerDown = React.useRef(false) const pointerDown = useRef(false)
const pointerMoved = React.useRef(false) const pointerMoved = useRef(false)
const initialSizeRef = React.useRef([0, 0]) const initialSizeRef = useRef([0, 0])
const baseSize = React.useRef(size) const baseSize = useRef(size)
const latestPropSize = React.useRef(size) const latestPropSize = useRef(size)
const fix = { const fix = {
left: -1, left: -1,
right: 1, right: 1,
}[direction] }[direction]
React.useEffect(() => { useEffect(() => {
latestPropSize.current = size latestPropSize.current = size
}, [size]) }, [size])
React.useEffect(() => { useEffect(() => {
const onPointerMove = ({ clientX, clientY }: PointerEvent) => { const onPointerMove = ({ clientX, clientY }: PointerEvent) => {
if (!pointerDown.current) return if (!pointerDown.current) return
const [x0, y0] = initialSizeRef.current const [x0, y0] = initialSizeRef.current
@ -48,7 +48,7 @@ export function useResizeHandler(
return () => window.removeEventListener('pointermove', onPointerMove) return () => window.removeEventListener('pointermove', onPointerMove)
}, [onResize, distanceTolerance, fix]) }, [onResize, distanceTolerance, fix])
React.useEffect(() => { useEffect(() => {
const onPointerUp = (e: PointerEvent) => { const onPointerUp = (e: PointerEvent) => {
if (pointerDown.current) { if (pointerDown.current) {
pointerDown.current = false pointerDown.current = false
@ -64,7 +64,7 @@ export function useResizeHandler(
return () => window.removeEventListener('pointerup', onPointerUp) return () => window.removeEventListener('pointerup', onPointerUp)
}, [onClick, onResizeStateChange]) }, [onClick, onResizeStateChange])
const onPointerDown = React.useCallback( const onPointerDown = useCallback(
(e: React.PointerEvent) => { (e: React.PointerEvent) => {
e.preventDefault() // Prevent unexpected selection when dragging in Safari e.preventDefault() // Prevent unexpected selection when dragging in Safari
const { clientX, clientY } = e const { clientX, clientY } = e

View file

@ -1,9 +1,9 @@
import * as React from 'react' import React, { useMemo, useState } from 'react'
export function useStateIO<S>(initialState: S | (() => S)): { export function useStateIO<S>(initialState: S | (() => S)): {
value: S value: S
onChange: React.Dispatch<React.SetStateAction<S>> onChange: React.Dispatch<React.SetStateAction<S>>
} { } {
const [value, onChange] = React.useState(initialState) const [value, onChange] = useState(initialState)
return React.useMemo(() => ({ value, onChange }), [value]) return useMemo(() => ({ value, onChange }), [value])
} }

View file

@ -1,9 +1,9 @@
import { IN_PRODUCTION_MODE } from 'env' import { IN_PRODUCTION_MODE } from 'env'
import * as React from 'react' import { useEffect, useRef } from 'react'
export function useUpdateReason<P extends Record<string, unknown>>(props: P) { export function useUpdateReason<P extends Record<string, unknown>>(props: P) {
const lastPropsRef = React.useRef<P>(props) const lastPropsRef = useRef<P>(props)
React.useEffect(() => { useEffect(() => {
if (IN_PRODUCTION_MODE) return if (IN_PRODUCTION_MODE) return
const output: ([string, keyof P, P[keyof P]] | [string, keyof P, P[keyof P], P[keyof P]])[] = [] const output: ([string, keyof P, P[keyof P]] | [string, keyof P, P[keyof P], P[keyof P]])[] = []
for (const key of Object.keys(props)) { for (const key of Object.keys(props)) {