mirror of
https://github.com/EnixCoda/Gitako.git
synced 2026-03-11 08:54:44 +00:00
refactor: no import * as from react
This commit is contained in:
parent
c3de9ad835
commit
03e3454afc
73 changed files with 250 additions and 251 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 = () =>
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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],
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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],
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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])
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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))}</>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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],
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 '.'
|
||||||
|
|
|
||||||
|
|
@ -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 '.'
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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 '.'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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 })
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import * as React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
export type PortalContextShape = string | null
|
export type PortalContextShape = string | null
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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}>
|
||||||
|
|
|
||||||
|
|
@ -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: () => {
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
? {
|
? {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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`
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 => {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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])
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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])
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue