mirror of
https://github.com/EnixCoda/Gitako.git
synced 2026-03-11 08:54:44 +00:00
refactor: migrate few files to TS
This commit is contained in:
parent
699d718d71
commit
6f6740f658
5 changed files with 86 additions and 52 deletions
|
|
@ -2,11 +2,11 @@ import { version } from '../package.json'
|
|||
// TODO: set this through ENV or something else
|
||||
const LOG_ENDPOINT = 'https://enix.one/gitako/log'
|
||||
|
||||
export function raiseError(error) {
|
||||
export function raiseError(error: Error) {
|
||||
return reportError(error)
|
||||
}
|
||||
|
||||
export function withErrorLog(method, args) {
|
||||
export function withErrorLog(method: Function, args: any[]) {
|
||||
return [
|
||||
function() {
|
||||
try {
|
||||
|
|
@ -19,13 +19,13 @@ export function withErrorLog(method, args) {
|
|||
]
|
||||
}
|
||||
|
||||
function encodeParams(params) {
|
||||
function encodeParams(params: any) {
|
||||
return Object.keys(params)
|
||||
.map(key => `${key}=${encodeURIComponent(JSON.stringify(params[key]))}`)
|
||||
.join('&')
|
||||
}
|
||||
|
||||
function reportError(error) {
|
||||
function reportError(error: Error) {
|
||||
return fetch(
|
||||
`${LOG_ENDPOINT}?${encodeParams({
|
||||
error: (error && error.message) || error,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,19 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import * as React from 'react'
|
||||
|
||||
export default class AutoSizer extends React.Component {
|
||||
static propTypes = {}
|
||||
type Props = {
|
||||
children(size: Size): React.ReactNode
|
||||
}
|
||||
|
||||
static defaultProps = {}
|
||||
type Size = {
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
type State = {
|
||||
size: Size
|
||||
}
|
||||
|
||||
export default class AutoSizer extends React.Component<Props, State> {
|
||||
state = {
|
||||
size: {
|
||||
width: 0,
|
||||
|
|
@ -13,7 +21,7 @@ export default class AutoSizer extends React.Component {
|
|||
},
|
||||
}
|
||||
|
||||
ref = React.createRef()
|
||||
ref = React.createRef<HTMLDivElement>()
|
||||
|
||||
componentDidMount() {
|
||||
const container = this.ref.current
|
||||
|
|
@ -69,7 +69,7 @@ function getSVGIconComponent(type) {
|
|||
|
||||
const iconStyle = { width: '100%', height: '100%' }
|
||||
|
||||
export default function Icon({ type, className, ...otherProps }) {
|
||||
export default function Icon({ type, className = undefined, ...otherProps }) {
|
||||
return (
|
||||
<div className={cx('octicon-wrapper', className)} {...otherProps}>
|
||||
{React.createElement(getSVGIconComponent(type), iconStyle)}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import React from 'react'
|
||||
import * as React from 'react'
|
||||
import Icon from 'components/Icon'
|
||||
import cx from 'utils/cx'
|
||||
import DOMHelper from 'utils/DOMHelper'
|
||||
import LoadingIndicator from 'components/LoadingIndicator';
|
||||
import LoadingIndicator from 'components/LoadingIndicator'
|
||||
import { TreeNode } from 'utils/VisibleNodesGenerator'
|
||||
|
||||
function getIconType(node) {
|
||||
function getIconType(node: TreeNode) {
|
||||
switch (node.type) {
|
||||
case 'tree':
|
||||
return 'folder'
|
||||
|
|
@ -15,8 +15,15 @@ function getIconType(node) {
|
|||
}
|
||||
}
|
||||
|
||||
export default class Node extends React.PureComponent {
|
||||
onClick = (event) => {
|
||||
type Props = {
|
||||
node: TreeNode
|
||||
onClick(node: TreeNode): void
|
||||
depth: number
|
||||
expanded: boolean
|
||||
focused: boolean
|
||||
}
|
||||
export default class Node extends React.PureComponent<Props> {
|
||||
onClick: React.MouseEventHandler = event => {
|
||||
if (event.metaKey) return
|
||||
event.preventDefault()
|
||||
const { node, onClick } = this.props
|
||||
|
|
@ -19,19 +19,28 @@
|
|||
* v stable
|
||||
*/
|
||||
|
||||
export type TreeNode = {
|
||||
name: string
|
||||
contents?: TreeNode[]
|
||||
path: string
|
||||
url?: string
|
||||
virtual?: boolean
|
||||
type: 'tree' | 'blob' | 'commit' | 'virtual'
|
||||
}
|
||||
|
||||
const SEARCH_DELAY = 250
|
||||
|
||||
function getFilterFunc(keyRegex) {
|
||||
return function filterFunc({ name }) {
|
||||
function getFilterFunc(keyRegex: RegExp) {
|
||||
return function filterFunc({ name }: TreeNode) {
|
||||
return keyRegex.test(name)
|
||||
}
|
||||
}
|
||||
|
||||
function filterDuplications(arr) {
|
||||
function filterDuplications<T>(arr: T[]) {
|
||||
return Array.from(new Set(arr))
|
||||
}
|
||||
|
||||
function search(treeNodes, searchKey) {
|
||||
function search(treeNodes: TreeNode[], searchKey: string): TreeNode[] {
|
||||
if (!searchKey) return treeNodes
|
||||
/**
|
||||
* if searchKey is 'abcd'
|
||||
|
|
@ -53,17 +62,21 @@ function search(treeNodes, searchKey) {
|
|||
return filterDuplications(searchResults)
|
||||
}
|
||||
|
||||
function debounce(func, delay) {
|
||||
let timer
|
||||
return (...args) => new Promise(resolve => {
|
||||
window.clearTimeout(timer)
|
||||
timer = window.setTimeout(() => resolve(func(...args)), delay)
|
||||
})
|
||||
function debounce<T, Arg>(
|
||||
func: (...args: Arg[]) => T,
|
||||
delay: number
|
||||
): (...args: Arg[]) => Promise<T> {
|
||||
let timer: number
|
||||
return (...args) =>
|
||||
new Promise(resolve => {
|
||||
window.clearTimeout(timer)
|
||||
timer = window.setTimeout(() => resolve(func(...args)), delay)
|
||||
})
|
||||
}
|
||||
|
||||
export const debouncedSearch = debounce(search, SEARCH_DELAY)
|
||||
|
||||
function getNodes(root, nodes = []) {
|
||||
function getNodes(root: TreeNode, nodes: TreeNode[] = []) {
|
||||
if (!root.contents) return
|
||||
root.contents.forEach(node => {
|
||||
nodes.push(node)
|
||||
|
|
@ -72,7 +85,7 @@ function getNodes(root, nodes = []) {
|
|||
return nodes
|
||||
}
|
||||
|
||||
function compressTree(root, prefix = []) {
|
||||
function compressTree(root: TreeNode, prefix: string[] = []): TreeNode {
|
||||
if (root.contents) {
|
||||
if (root.contents.length === 1) {
|
||||
const singleton = root.contents[0]
|
||||
|
|
@ -84,35 +97,33 @@ function compressTree(root, prefix = []) {
|
|||
return {
|
||||
...root,
|
||||
name: [...prefix, root.name].join('/'),
|
||||
contents: root.contents
|
||||
? root.contents.map(node => compressTree(node))
|
||||
: undefined,
|
||||
contents: root.contents ? root.contents.map(node => compressTree(node)) : undefined,
|
||||
}
|
||||
}
|
||||
|
||||
export default class VisibleNodesGenerator {
|
||||
// LEVEL 1
|
||||
root = null
|
||||
nodes = null
|
||||
root: TreeNode = null
|
||||
nodes: TreeNode[] = null
|
||||
compress = false
|
||||
compressed = false
|
||||
compressedRoot: TreeNode | undefined
|
||||
|
||||
getRoot() {
|
||||
return this.compress && this.compressed
|
||||
? this.compressedRoot
|
||||
: this.root
|
||||
return this.compress && this.compressed ? this.compressedRoot : this.root
|
||||
}
|
||||
|
||||
async plantTree(root) {
|
||||
async plantTree(root: TreeNode) {
|
||||
this.root = root
|
||||
this.nodes = getNodes(root)
|
||||
this.compressedRoot = compressTree(root)
|
||||
|
||||
await this.search()
|
||||
await this.search('')
|
||||
}
|
||||
|
||||
// LEVEL 2
|
||||
searchedNodes = null
|
||||
async search(searchKey) {
|
||||
searchedNodes: TreeNode[] | null = null
|
||||
async search(searchKey: string) {
|
||||
this.compressed = !Boolean(searchKey)
|
||||
this.searchedNodes = searchKey
|
||||
? await debouncedSearch(this.nodes, searchKey)
|
||||
|
|
@ -122,18 +133,18 @@ export default class VisibleNodesGenerator {
|
|||
this.generateVisibleNodes()
|
||||
}
|
||||
|
||||
setCompress(compress) {
|
||||
setCompress(compress: VisibleNodesGenerator['compress']) {
|
||||
this.compress = compress
|
||||
}
|
||||
|
||||
// LEVEL 3
|
||||
expandedNodes = new Set()
|
||||
expandedNodes = new Set<TreeNode>()
|
||||
depths = new Map()
|
||||
toggleExpand(node) {
|
||||
toggleExpand(node: TreeNode) {
|
||||
this.setExpand(node, !this.expandedNodes.has(node))
|
||||
}
|
||||
|
||||
setExpand(node, expand) {
|
||||
setExpand(node: TreeNode, expand: boolean) {
|
||||
if (expand && node.contents) {
|
||||
// only node with contents is expandable
|
||||
this.expandedNodes.add(node)
|
||||
|
|
@ -143,9 +154,9 @@ export default class VisibleNodesGenerator {
|
|||
this.generateVisibleNodes()
|
||||
}
|
||||
|
||||
expandTo(path) {
|
||||
expandTo(path: string) {
|
||||
let root = this.getRoot()
|
||||
const findNode = (root) => {
|
||||
const findNode: (root: TreeNode) => TreeNode = root => {
|
||||
if (path.indexOf(root.path) === 0) {
|
||||
if (root.path === path) return root
|
||||
this.setExpand(root, true)
|
||||
|
|
@ -165,13 +176,20 @@ export default class VisibleNodesGenerator {
|
|||
return node
|
||||
}
|
||||
|
||||
visibleNodes = null
|
||||
visibleNodes: {
|
||||
nodes: VisibleNodesGenerator['nodes']
|
||||
depths: VisibleNodesGenerator['depths']
|
||||
expandedNodes: VisibleNodesGenerator['expandedNodes']
|
||||
focusedNode: TreeNode
|
||||
} = null
|
||||
generateVisibleNodes() {
|
||||
this.focusedNode = null
|
||||
this.depths.clear()
|
||||
const nodesSet = new Set() // prevent duplication
|
||||
const nodes = [], stack = this.searchedNodes.slice().reverse()
|
||||
let current, depth = 0
|
||||
const nodes = [],
|
||||
stack = this.searchedNodes.slice().reverse()
|
||||
let current,
|
||||
depth = 0
|
||||
while (stack.length) {
|
||||
current = stack.pop()
|
||||
if (current === null) {
|
||||
|
|
@ -192,13 +210,14 @@ export default class VisibleNodesGenerator {
|
|||
nodes,
|
||||
depths: this.depths,
|
||||
expandedNodes: this.expandedNodes,
|
||||
focusedNode: null,
|
||||
}
|
||||
this.focusNode(null)
|
||||
}
|
||||
|
||||
// LEVEL 4
|
||||
focusedNode = null
|
||||
focusNode(node) {
|
||||
focusedNode: TreeNode = null
|
||||
focusNode(node: TreeNode) {
|
||||
this.focusedNode = node
|
||||
this.visibleNodes = {
|
||||
...this.visibleNodes,
|
||||
Loading…
Reference in a new issue