refactor: migrate few files to TS

This commit is contained in:
EnixCoda 2019-01-06 22:04:56 +08:00
parent 699d718d71
commit 6f6740f658
No known key found for this signature in database
GPG key ID: 0C1A07377913A1DD
5 changed files with 86 additions and 52 deletions

View file

@ -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,

View file

@ -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

View file

@ -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)}

View file

@ -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

View file

@ -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,