mirror of
https://github.com/woodchen-ink/clash-and-dashboard.git
synced 2025-07-18 14:01:56 +08:00
Update: refactor proxy
This commit is contained in:
parent
498e7c1f7c
commit
27af0a636b
@ -3,6 +3,6 @@
|
||||
.card {
|
||||
padding: 15px;
|
||||
box-shadow: 0 0 20px rgba($color-primary-dark, 0.2);
|
||||
background-color: #fff;
|
||||
background-color: $color-white;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ $width: 32px;
|
||||
height: $switch-radius;
|
||||
width: $switch-radius;
|
||||
border-radius: $switch-radius / 2;
|
||||
background-color: #fff;
|
||||
background-color: $color-white;
|
||||
box-shadow: 0 0 8px rgba($color-primary-dark, 0.4);
|
||||
transition: transform 0.3s ease;
|
||||
transform: translateX($width - $switch-radius + $switch-offset);
|
||||
@ -54,6 +54,6 @@ $width: 32px;
|
||||
.switch-icon {
|
||||
position: absolute;
|
||||
transform: translateX(13px) scale(0.4);
|
||||
color: #fff;
|
||||
color: $color-white;
|
||||
line-height: $height;
|
||||
}
|
||||
|
@ -0,0 +1,76 @@
|
||||
import * as React from 'react'
|
||||
import { translate } from 'react-i18next'
|
||||
import classnames from 'classnames'
|
||||
import { BaseComponentProps, Proxy as IProxy, I18nProps, TagColors } from '@models'
|
||||
import { Modal, Row, Col } from '@components'
|
||||
import { getLocalStorageItem, setLocalStorageItem } from '@lib/helper'
|
||||
import './style.scss'
|
||||
|
||||
interface ModifyProxyDialogProps extends BaseComponentProps, I18nProps {
|
||||
config: IProxy
|
||||
onOk?: (config: IProxy) => void
|
||||
onCancel?: () => void
|
||||
}
|
||||
|
||||
interface ModifyProxyDialogState {
|
||||
config: IProxy
|
||||
currentColor: string
|
||||
}
|
||||
|
||||
class RawDialog extends React.Component<ModifyProxyDialogProps, ModifyProxyDialogState> {
|
||||
|
||||
constructor (props: ModifyProxyDialogProps) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
config: props.config,
|
||||
currentColor: getLocalStorageItem(props.config.name)
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
console.log(this.props.config)
|
||||
}
|
||||
|
||||
handleOk = () => {
|
||||
const { onOk } = this.props
|
||||
const { config, currentColor } = this.state
|
||||
setLocalStorageItem(config.name, currentColor)
|
||||
|
||||
onOk(config)
|
||||
}
|
||||
|
||||
render () {
|
||||
const { onCancel, t } = this.props
|
||||
const { currentColor } = this.state
|
||||
|
||||
return <Modal
|
||||
className="proxy-editor"
|
||||
title={t('editDialog.title')}
|
||||
onOk={this.handleOk}
|
||||
onClose={onCancel}
|
||||
>
|
||||
<Row gutter={24} style={{ padding: '12px 0' }}>
|
||||
<Col span={6} style={{ paddingLeft: 0 }}>{t('editDialog.color')}</Col>
|
||||
<Col span={18}>
|
||||
<div className="proxy-editor-color-selector">
|
||||
{
|
||||
TagColors.map(color => (
|
||||
<span
|
||||
className={classnames('color-item', {
|
||||
'color-item-active': currentColor === color
|
||||
})}
|
||||
key={color}
|
||||
style={{ background: color }}
|
||||
onClick={() => this.setState({ currentColor: color })}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Modal>
|
||||
}
|
||||
}
|
||||
|
||||
export const ModifyProxyDialog = translate(['Proxies'])(RawDialog)
|
@ -0,0 +1,29 @@
|
||||
@import '~@styles/variables';
|
||||
|
||||
.proxy-editor {
|
||||
.proxy-editor-color-selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.color-item {
|
||||
position: relative;
|
||||
margin-right: 10px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.color-item-active::after {
|
||||
position: absolute;
|
||||
left: -3px;
|
||||
top: -3px;
|
||||
content: '';
|
||||
display: block;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid $color-gray-dark;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,24 +1,51 @@
|
||||
import * as React from 'react'
|
||||
import classnames from 'classnames'
|
||||
import { BaseComponentProps, Proxy as IProxy } from '@models'
|
||||
import { Icon } from '@components'
|
||||
import { BaseComponentProps, Proxy as IProxy, TagColors } from '@models'
|
||||
import { getProxyDelay } from '@lib/request'
|
||||
import { to } from '@lib/helper'
|
||||
import { to, getLocalStorageItem, setLocalStorageItem, sample, noop } from '@lib/helper'
|
||||
import './style.scss'
|
||||
|
||||
interface ProxyProps extends BaseComponentProps {
|
||||
config: IProxy
|
||||
onEdit?: (e: React.MouseEvent<HTMLElement>) => void
|
||||
}
|
||||
|
||||
interface ProxyState {
|
||||
delay: number
|
||||
hasError: boolean
|
||||
color: string
|
||||
}
|
||||
|
||||
export class Proxy extends React.Component<ProxyProps , ProxyState> {
|
||||
|
||||
state = {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
const { config } = props
|
||||
const { name } = config
|
||||
let color = getLocalStorageItem(name)
|
||||
|
||||
if (!color) {
|
||||
color = sample(TagColors)
|
||||
setLocalStorageItem(name, color)
|
||||
}
|
||||
|
||||
this.state = {
|
||||
delay: -1,
|
||||
hasError: false
|
||||
hasError: false,
|
||||
color
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUpdate () {
|
||||
const { config: { name } } = this.props
|
||||
const { color: rawColor } = this.state
|
||||
const color = getLocalStorageItem(name)
|
||||
|
||||
if (rawColor !== color) {
|
||||
this.setState({ color })
|
||||
}
|
||||
}
|
||||
|
||||
async componentDidMount () {
|
||||
@ -34,13 +61,16 @@ export class Proxy extends React.Component<ProxyProps , ProxyState> {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { config, className } = this.props
|
||||
const { delay, hasError } = this.state
|
||||
const { config, className, onEdit = noop } = this.props
|
||||
const { delay, color, hasError } = this.state
|
||||
const backgroundColor = hasError ? undefined : color
|
||||
|
||||
return (
|
||||
<div className={classnames('proxy-item', { 'proxy-error': hasError }, className)}>
|
||||
<span className="proxy-name">{config.name}</span>
|
||||
<span className="proxy-delay">{delay === -1 ? '-' : `${delay}s`}</span>
|
||||
<span className="proxy-type" style={{ backgroundColor }}>{config.type}</span>
|
||||
<p className="proxy-name">{config.name}</p>
|
||||
<p className="proxy-delay">{delay === -1 ? '-' : `${delay}ms`}</p>
|
||||
<Icon className="proxy-editor" type="setting" onClick={onEdit} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -1,37 +1,65 @@
|
||||
@import '~@styles/variables';
|
||||
|
||||
.proxy-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
box-shadow: 0 0 20px rgba($color-primary-dark, 0.2);
|
||||
position: relative;
|
||||
padding: 10px;
|
||||
height: 110px;
|
||||
width: 110px;
|
||||
border-radius: 4px;
|
||||
background: $color-white;
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
box-shadow: 0 0 20px rgba($color-primary-dark, 0.2);
|
||||
transition: all 300ms ease;
|
||||
|
||||
.proxy-icon {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
.proxy-type {
|
||||
padding: 2px 5px;
|
||||
font-size: 10px;
|
||||
color: $color-white;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.proxy-name {
|
||||
width: 80%;
|
||||
margin-top: 8px;
|
||||
color: $color-primary-dark;
|
||||
max-height: 30px;
|
||||
margin-top: 10px;
|
||||
color: $color-primary-darken;
|
||||
font-size: 12px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.proxy-delay {
|
||||
width: 80%;
|
||||
margin-top: 8px;
|
||||
color: $color-primary-dark;
|
||||
font-size: 12px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
bottom: 13px;
|
||||
font-size: 10px;
|
||||
color: rgba($color: $color-primary-darken, $alpha: 0.8);
|
||||
}
|
||||
|
||||
.proxy-editor {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
cursor: pointer;
|
||||
color: rgba($color: $color-primary-darken, $alpha: 0.8);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: all 300ms ease;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 10px 20px rgba($color-primary-darken, 0.4);
|
||||
|
||||
.proxy-editor {
|
||||
opacity: 1;
|
||||
pointer-events: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.proxy-error {
|
||||
opacity: 0.5;
|
||||
|
||||
.proxy-type {
|
||||
background-color: $color-gray-darken;
|
||||
}
|
||||
}
|
||||
|
@ -1 +1,2 @@
|
||||
export * from './Proxy'
|
||||
export * from './ModifyProxyDialog'
|
||||
|
@ -3,16 +3,26 @@ import { translate } from 'react-i18next'
|
||||
import { inject, observer } from 'mobx-react'
|
||||
import { storeKeys } from '@lib/createStore'
|
||||
import { Header, Icon } from '@components'
|
||||
import { I18nProps, BaseRouterProps } from '@models'
|
||||
import { I18nProps, BaseRouterProps, Proxy as IProxy } from '@models'
|
||||
|
||||
import { Proxy } from './components'
|
||||
import { Proxy, ModifyProxyDialog } from './components'
|
||||
import './style.scss'
|
||||
|
||||
interface ProxiesProps extends BaseRouterProps, I18nProps {}
|
||||
|
||||
interface ProxiesState {
|
||||
showModifyProxyDialog: boolean
|
||||
activeConfig?: IProxy
|
||||
}
|
||||
|
||||
@inject(...storeKeys)
|
||||
@observer
|
||||
class Proxies extends React.Component<ProxiesProps, {}> {
|
||||
class Proxies extends React.Component<ProxiesProps, ProxiesState> {
|
||||
|
||||
state = {
|
||||
showModifyProxyDialog: false,
|
||||
activeConfig: null
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.props.config.fetchAndParseConfig()
|
||||
@ -20,8 +30,10 @@ class Proxies extends React.Component<ProxiesProps, {}> {
|
||||
|
||||
render () {
|
||||
const { t, config } = this.props
|
||||
const { showModifyProxyDialog, activeConfig } = this.state
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="page">
|
||||
<div className="proxies-container">
|
||||
<Header title={t('title')} >
|
||||
@ -30,13 +42,14 @@ class Proxies extends React.Component<ProxiesProps, {}> {
|
||||
{
|
||||
config.state === 'ok' && <ul className="proxies-list">
|
||||
{
|
||||
config.config.proxy.map(
|
||||
(p, index) => (
|
||||
config.config.proxy.map((p, index) => (
|
||||
<li key={index}>
|
||||
<Proxy config={p} />
|
||||
<Proxy config={p} onEdit={() => this.setState({
|
||||
showModifyProxyDialog: true,
|
||||
activeConfig: p
|
||||
})} />
|
||||
</li>
|
||||
)
|
||||
)
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
@ -44,7 +57,19 @@ class Proxies extends React.Component<ProxiesProps, {}> {
|
||||
<div className="proxies-container">
|
||||
<Header title={t('groupTitle')} />
|
||||
</div>
|
||||
|
||||
{
|
||||
showModifyProxyDialog && <ModifyProxyDialog
|
||||
config={activeConfig}
|
||||
onOk={config => {
|
||||
console.log(config)
|
||||
this.setState({ showModifyProxyDialog: false, activeConfig: null })
|
||||
}}
|
||||
onCancel={() => this.setState({ showModifyProxyDialog: false, activeConfig: null })}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,11 @@
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
margin: 20px 15px 20px 0;
|
||||
margin: 8px 0;
|
||||
margin-right: 15px;
|
||||
|
||||
&:nth-child(6n) {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +52,7 @@
|
||||
|
||||
> i {
|
||||
transform: scale(0.5);
|
||||
color: #fff;
|
||||
color: $color-white;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,15 @@
|
||||
@import '~@styles/variables';
|
||||
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
width: 140px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.sidebar-logo {
|
||||
@ -42,7 +46,7 @@
|
||||
|
||||
> a.active {
|
||||
background: linear-gradient(135deg, $color-primary, $color-primary-dark);
|
||||
color: #fff;
|
||||
color: $color-white;
|
||||
box-shadow: 0 2px 8px rgba($color: $color-primary-dark, $alpha: 0.5);
|
||||
}
|
||||
}
|
||||
|
@ -43,6 +43,21 @@ export default {
|
||||
},
|
||||
Proxies: {
|
||||
title: 'Proxies',
|
||||
editDialog: {
|
||||
title: 'Edit Proxy',
|
||||
color: 'Color',
|
||||
name: 'Name',
|
||||
type: 'Type',
|
||||
server: 'Server',
|
||||
port: 'Port',
|
||||
password: 'Password',
|
||||
cipher: 'Cipher',
|
||||
obfs: 'Obfs',
|
||||
'obfs-host': 'Obfs-host',
|
||||
uuid: 'Uuid',
|
||||
alterid: 'Alterid',
|
||||
tls: 'TLS'
|
||||
},
|
||||
groupTitle: 'Policy Group'
|
||||
}
|
||||
}
|
||||
|
@ -43,6 +43,21 @@ export default {
|
||||
},
|
||||
Proxies: {
|
||||
title: '代理',
|
||||
editDialog: {
|
||||
title: '编辑代理',
|
||||
color: '颜色',
|
||||
name: '名字',
|
||||
type: '类型',
|
||||
server: '服务器',
|
||||
port: '端口',
|
||||
password: '密码',
|
||||
cipher: '加密方式',
|
||||
obfs: 'Obfs',
|
||||
'obfs-host': 'Obfs-host',
|
||||
uuid: 'Uuid',
|
||||
alterid: 'Alterid',
|
||||
tls: 'TLS'
|
||||
},
|
||||
groupTitle: '策略组'
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,16 @@ export function removeLocalStorageItem (key: string) {
|
||||
return window.localStorage.removeItem(key)
|
||||
}
|
||||
|
||||
export function randomNumber (min: number, max: number) {
|
||||
return (min + Math.random() * (max - min)) >> 0
|
||||
}
|
||||
|
||||
export function sample<T> (arr: T[]) {
|
||||
return arr[randomNumber(0, arr.length)]
|
||||
}
|
||||
|
||||
export function noop () {}
|
||||
|
||||
/**
|
||||
* to return Promise<[T, Error]>
|
||||
* @param {Promise<T>} promise
|
||||
|
7
src/models/TagColors.ts
Normal file
7
src/models/TagColors.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export const TagColors = [
|
||||
'#ff3e5e',
|
||||
'#686fff',
|
||||
'#ff9a28',
|
||||
'#b83fe6',
|
||||
'#00c520'
|
||||
]
|
@ -3,3 +3,4 @@ export * from './Config'
|
||||
export * from './Proxy'
|
||||
export * from './Rule'
|
||||
export * from './I18n'
|
||||
export * from './TagColors'
|
||||
|
@ -22,8 +22,8 @@ body {
|
||||
}
|
||||
|
||||
.app {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
padding-left: 150px;
|
||||
}
|
||||
|
||||
.app.clash-x {
|
||||
@ -32,13 +32,15 @@ body {
|
||||
|
||||
.page-container {
|
||||
width: 100%;
|
||||
margin: 20px 0;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.page {
|
||||
padding: 10px 35px;
|
||||
padding: 20px 35px;
|
||||
padding-left: 0;
|
||||
padding-bottom: 30px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
height: 100vh;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
@font-face {
|
||||
font-family: "clash-iconfont";
|
||||
src: url('//at.alicdn.com/t/font_841708_h6oakuryxxb.ttf') format('truetype');
|
||||
src: url('//at.alicdn.com/t/font_841708_7ge2gqse7qv.ttf') format('truetype');
|
||||
}
|
||||
|
||||
.clash-iconfont {
|
||||
@ -37,3 +37,5 @@
|
||||
.icon-info::before { content: "\e60b"; }
|
||||
|
||||
.icon-info-o::before { content: "\e60c"; }
|
||||
|
||||
.icon-setting::before { content: "\e60d"; }
|
||||
|
Loading…
x
Reference in New Issue
Block a user