& { src: string | string[] }> =
10 | forwardRef(({ src, className = '', ...props }, ref) =>
11 |
20 |

}
23 | unloader={

}
24 | />
25 |

26 |
27 | )
28 |
29 | export default Avatar
30 |
--------------------------------------------------------------------------------
/src/components/Dots.tsx:
--------------------------------------------------------------------------------
1 | import './dots.less'
2 | import React from 'react'
3 | import ToolTip from 'rc-tooltip'
4 |
5 | interface P {
6 | count: number
7 | active: number
8 | names: string[]
9 | onChange: (p: number) => void
10 | }
11 | const Dots: React.FC = props => {
12 | const arr = new Array(props.count)
13 | for (let i = 0; i < props.count; i++) {
14 | const active = i === props.active
15 | arr[i] = {} : () => props.onChange(i)}
19 | className={active ? 'active' : undefined}
20 | />
21 |
22 | }
23 | return
24 | {arr}
25 |
26 | }
27 |
28 | export default Dots
29 |
--------------------------------------------------------------------------------
/src/components/Dropdown.tsx:
--------------------------------------------------------------------------------
1 | import './dropdown.less'
2 | import React, { useState } from 'react'
3 | import { motion } from 'framer-motion'
4 |
5 | const poses = {
6 | open: { width: 'auto' },
7 | closed: { width: 0 }
8 | }
9 | const Dropdown: React.FC<{ open: boolean }> = (props) => {
10 | const [hover, setHover] = useState(false)
11 | return
setHover(true)}
14 | onMouseLeave={() => setHover(false)}
15 | animate={props.open || hover ? poses.open : poses.closed}
16 | className='dropdown'
17 | >
18 | {props.children}
19 |
20 | }
21 |
22 | export default Dropdown
23 |
--------------------------------------------------------------------------------
/src/components/Empty.tsx:
--------------------------------------------------------------------------------
1 | import './empty.less'
2 | import React from 'react'
3 |
4 | export type Props = React.HTMLAttributes
& { text?: string }
5 | const Empty: React.FC = ({ text, className = '', ...props }) =>
6 |
7 |
8 |
{text || $('No Data')}
9 |
10 |
11 | export default Empty
12 |
--------------------------------------------------------------------------------
/src/components/ErrorHandler.ts:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import history from '../utils/history'
3 |
4 | const errorComponents: Record any> = { }
5 | history.listen(() => {
6 | const f = errorComponents[history.location.pathname]
7 | if (f) f()
8 | })
9 |
10 | export const AUTO_RELOAD = { autoReload: true }
11 |
12 | export default class ErrorHandler extends React.Component<{
13 | onError?: (e: any, info: string) => boolean
14 | autoReload?: boolean
15 | }> {
16 | public state = { error: false }
17 | private cachePath: string
18 | public static getDerivedStateFromError () { return { error: true } }
19 | public componentDidCatch (e, info) {
20 | if (this.props.autoReload) {
21 | errorComponents[this.cachePath = history.location.pathname] = () => this.setState({ error: false })
22 | }
23 | if (!this.props.onError || !this.props.onError(e, info)) history.push('/error')
24 | }
25 | public UNSAFE_componentWillUpdate () {
26 | if (this.cachePath) {
27 | delete errorComponents[this.cachePath]
28 | this.cachePath = null
29 | }
30 | }
31 | public render () {
32 | return this.state.error ? React.createElement('div', {}) : this.props.children
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/components/IconButton.tsx:
--------------------------------------------------------------------------------
1 | import './icon-button.css'
2 | import React, { useMemo, forwardRef } from 'react'
3 |
4 | export const images = Object.values(require('../assets/images/terracotta/*.png'))
5 | .sort(() => 0.5 - Math.random()) as string[]
6 |
7 | export type Props = { title: string, icon?: string, hideFirst?: boolean } & React.HTMLAttributes
8 |
9 | let i = 0
10 | const IconButton: React.FC = forwardRef(({ icon, title, hideFirst, ...props }, ref) => {
11 | const src = useMemo(() => icon || (images[images.length <= i ? (i = 0) : i++]), [icon])
12 | const t = title[0].toUpperCase()
13 | const c = t.charCodeAt(0)
14 | return
15 |
16 | {(!hideFirst || !icon) && = 65 && c <= 90) || (c >= 48 && c <= 57) ? 'offset' : undefined}
19 | >
20 | {t}}
21 | {title}
22 |
23 | })
24 |
25 | export default IconButton
26 |
--------------------------------------------------------------------------------
/src/components/IconPicker.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable jsx-a11y/no-noninteractive-element-to-interactive-role */
2 | import './icon-picker.css'
3 | import React from 'react'
4 | import Dialog from 'rc-dialog'
5 |
6 | export const icons: Record = require('../assets/images/icons/*.png')
7 | export const resolveIcon = (url: string): string => url in icons ? icons[url] : typeof url === 'string' &&
8 | url.startsWith('data:') ? url : icons.Grass
9 |
10 | const IconPicker: React.FC<{ open: boolean, onClose: (icon: string | null) => void }> = p => {
11 | return
24 | }
25 | export default IconPicker
26 |
--------------------------------------------------------------------------------
/src/components/InstallList.tsx:
--------------------------------------------------------------------------------
1 | import './install-list.less'
2 | import React, { useState, useMemo } from 'react'
3 | import Dialog from 'rc-dialog'
4 | import Treebeard from './treebeard/index'
5 | import * as T from '../protocol/types'
6 |
7 | let _setRes: any
8 | let resolve: any
9 | let reject: any
10 | let view: T.InstallView
11 | global.__requestInstallResources = (d: any, _view: any) => {
12 | if (reject) reject()
13 | view = _view
14 | const ret = new Promise(a => {
15 | resolve = () => {
16 | _setRes(null)
17 | resolve = reject = null
18 | a(true)
19 | }
20 | reject = () => {
21 | _setRes(null)
22 | resolve = reject = null
23 | a(false)
24 | }
25 | })
26 | _setRes(d)
27 | return ret
28 | }
29 |
30 | const InstallList: React.FC = () => {
31 | const [res, setRes] = useState(null)
32 | _setRes = setRes
33 | const [data, setData] = useState({})
34 | useMemo(() => {
35 | if (res && res.type === 'Version') {
36 | const mods = []
37 | const servers = []
38 | const resources = []
39 | const plugins = []
40 | const r = res as T.ResourceVersion
41 | if (typeof r.resources === 'object') {
42 | Object.values(r.resources).forEach(it => {
43 | switch (it.type) {
44 | case 'Mod':
45 | mods.push({ id: it.id, name: `${it.title || it.id}@${it.version}` })
46 | break
47 | case 'Server':
48 | servers.push({ id: it.ip, name: it.title || (it.port ? `${it.ip}:${it.port}` : it.ip) })
49 | break
50 | case 'ResourcePack':
51 | resources.push({ id: it.id, name: `${it.title || it.id}@${it.version}` })
52 | break
53 | }
54 | })
55 | }
56 | const d = {
57 | id: 'root',
58 | name: $('Detailed List'),
59 | toggled: true,
60 | children: []
61 | }
62 | if (mods.length) d.children.push({ id: 'mods', name: $('Mods'), children: mods })
63 | if (servers.length) d.children.push({ id: 'servers', name: $('Servers'), children: servers })
64 | if (resources.length) d.children.push({ id: 'resources', name: $('ResourcePacks'), children: resources })
65 | if (plugins.length) d.children.push({ id: 'plugins', name: $('Plugins'), children: resources })
66 | setData(d)
67 | } else setData(res)
68 | }, [res])
69 |
70 | const [cursor, setCursor] = useState(null)
71 |
72 | if (!res) return
73 |
74 | let name: string
75 | let comp: any
76 | let title: string
77 | switch (res.type) {
78 | case 'Version': {
79 | name = $('Version')
80 | const r = res as any
81 | if (r.$vanilla) title = $('Vanilla Minecraft')
82 | else if (r.$forge) title = 'Forge'
83 | else if (r.$fabric) title = 'Fabric'
84 | const ver = r.$forge?.version || r.$fabric?.version
85 | comp =
86 | {r.mcVersion &&
{$('Minecraft Version')}: {r.mcVersion}
}
87 | {ver &&
{$('VersionId')}: {ver}
}
88 | {data?.children?.length ?
{
90 | if (cursor) cursor.active = false
91 | node.active = true
92 | if (node.children) node.toggled = toggled
93 | setCursor(node)
94 | setData(Object.assign({}, data))
95 | }}
96 | /> : null}
97 |
98 | break
99 | }
100 | case 'Mod': {
101 | name = $('Mods')
102 | const r = res as T.ResourceMod
103 | comp = <>
104 | {r.mcVersion && {$('Minecraft Version')}: {r.mcVersion}
}
105 | {r.apis && {$('Apis')}: {Object.keys(r.apis).join(', ')}
}
106 | >
107 | break
108 | }
109 | case 'ResourcePack':
110 | name = $('ResourcePacks')
111 | break
112 | case 'Server': {
113 | name = $('Servers')
114 | const r = res as T.ResourceServer
115 | comp = {$('Host')}: {r.ip}{r.port ? ':' + r.port : null}
116 | break
117 | }
118 | case 'Plugin':
119 | name = $('Plugins')
120 | break
121 | }
122 | return
140 | }
141 |
142 | export default InstallList
143 |
--------------------------------------------------------------------------------
/src/components/LiveRoute.tsx:
--------------------------------------------------------------------------------
1 | import CacheRoute, { CacheRouteProps } from 'react-router-cache-route'
2 | import React from 'react'
3 | import ErrorHandler, { AUTO_RELOAD } from './ErrorHandler'
4 |
5 | const hide = { className: 'route route-hide' }
6 | const show = { className: 'route route-show' }
7 | const behavior = (h: boolean) => h ? hide : show
8 | const LiveRoute: React.FC = p => React.createElement(ErrorHandler, AUTO_RELOAD,
9 | React.createElement(CacheRoute, { ...p, behavior }))
10 | export default LiveRoute
11 |
--------------------------------------------------------------------------------
/src/components/Loading.tsx:
--------------------------------------------------------------------------------
1 | import './loading.less'
2 | import React from 'react'
3 |
4 | export type Props = React.HTMLAttributes & { text?: string }
5 | const Loading: React.FC = ({ text, className = '', ...props }) =>
6 |
7 |
8 |
{text || $('Loading...')}
9 |
10 |
11 | export default Loading
12 |
--------------------------------------------------------------------------------
/src/components/LoginDialog.tsx:
--------------------------------------------------------------------------------
1 | import './login-dialog.less'
2 | import React, { useState, Dispatch, SetStateAction } from 'react'
3 | import Dialog from 'rc-dialog'
4 | import { shell } from 'electron'
5 | import { Link } from 'react-router-dom'
6 | import * as Auth from '../plugin/Authenticator'
7 |
8 | const getObjectLength = (obj: any) => {
9 | let i = 0
10 | /* eslint-disable @typescript-eslint/no-unused-vars */
11 | for (const _ in obj) i++
12 | return i
13 | }
14 |
15 | let fn: (type: string) => void
16 | let onClose2: () => void
17 | let defaults: Record | void
18 | let openFn: [boolean, Dispatch>]
19 | export const openLoginDialog = (type: string, defaultValues?: Record, onClose?: () => void) => {
20 | if (!type) return
21 | onClose2 = onClose
22 | defaults = defaultValues
23 | if (type) fn(type)
24 | if (openFn) openFn[1](true)
25 | }
26 |
27 | /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
28 | const LoginDialog: React.FC<{ open: boolean, onClose: () => void }> = props => {
29 | const [type, setType] = useState('')
30 | openFn = useState(false)
31 | fn = setType
32 | const [loading, setLoading] = useState(false)
33 | const [submitted, setSubmitted] = useState(false)
34 | const currentLogin = pluginMaster.logins[type]
35 | let width: number
36 | if (!type) {
37 | const len = getObjectLength(pluginMaster.logins)
38 | width = len > 3 ? 570 / len - 30 : 130
39 | }
40 | const close = () => {
41 | props.onClose()
42 | openFn[1](false)
43 | setType('')
44 | if (onClose2) onClose2()
45 | fn = onClose2 = defaults = null
46 | }
47 | const Component: React.ComponentType = currentLogin?.[Auth.COMPONENT]
48 | return