From 60b38e72368b6d7613074629376b6daf51756a17 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 16 Dec 2018 00:22:14 +0800 Subject: [PATCH] Chore: adjust architecture --- src/components/ButtonSelect/index.tsx | 5 +- src/components/Tags/index.tsx | 39 +++------ src/components/Tags/style.scss | 40 ++------- .../Proxies/components/Group/index.tsx | 33 +++++++ .../Proxies/components/Group/style.scss | 42 +++++++++ .../Proxies/components/Proxy/index.tsx | 24 ++--- src/containers/Proxies/components/index.ts | 1 + src/containers/Proxies/index.tsx | 87 +++++++------------ src/containers/Proxies/style.scss | 8 ++ src/containers/Rules/index.tsx | 8 +- src/containers/Settings/index.tsx | 6 +- src/lib/createStore.ts | 2 +- src/lib/helper.ts | 9 ++ src/lib/request.ts | 30 +++++-- src/models/BaseProps.ts | 2 +- src/models/Config.ts | 43 +++++++++ src/stores/ConfigStore.ts | 43 +++++++-- src/styles/variables.scss | 1 + 18 files changed, 261 insertions(+), 162 deletions(-) create mode 100644 src/containers/Proxies/components/Group/index.tsx create mode 100644 src/containers/Proxies/components/Group/style.scss diff --git a/src/components/ButtonSelect/index.tsx b/src/components/ButtonSelect/index.tsx index 1e23e01..ec85cfd 100644 --- a/src/components/ButtonSelect/index.tsx +++ b/src/components/ButtonSelect/index.tsx @@ -32,8 +32,9 @@ export class ButtonSelect extends React.Component { value={option.value} key={option.value} className={classnames('button-select-options', { actived: value === option.value })} - onClick={() => onSelect(option.value)} - >{option.label} + onClick={() => onSelect(option.value)}> + { option.label } + )) } diff --git a/src/components/Tags/index.tsx b/src/components/Tags/index.tsx index f6d2c22..89268b3 100644 --- a/src/components/Tags/index.tsx +++ b/src/components/Tags/index.tsx @@ -1,49 +1,32 @@ import * as React from 'react' import { BaseComponentProps } from '@models/BaseProps' -import { Icon } from '@components' import classnames from 'classnames' import './style.scss' interface TagsProps extends BaseComponentProps { data: Set - showAdd: boolean - onDelete: (tag: string) => void - onAdd: () => void + onClick: (name: string) => void + selected: string } export class Tags extends React.Component { - static defaultProps: TagsProps = { - data: new Set(), - showAdd: true, - onDelete: () => {}, - onAdd: () => {} - } - render () { - const { className, data, onDelete, onAdd, showAdd } = this.props + const { className, data, onClick, selected } = this.props + const tags = [...data] - .sort() - .map(t => ( -
  • + .sort() + .map(t => { + const tagClass = classnames({ 'tags-selected': selected === t }) + return ( +
  • onClick(t)}> { t } - onDelete(t)}/>
  • - )) + ) + }) return (
      { tags } - { - showAdd && -
    • - -
    • - }
    ) } diff --git a/src/components/Tags/style.scss b/src/components/Tags/style.scss index 837ab70..34404db 100644 --- a/src/components/Tags/style.scss +++ b/src/components/Tags/style.scss @@ -16,46 +16,18 @@ $delete-height: 22px; display: flex; align-items: center; justify-content: center; - border: 1px solid $color-primary-darken; + border: 1px solid $color-primary-dark; color: $color-primary-darken; height: $height; border-radius: $height / 2; padding: 0 6px; margin: 3px 4px; font-size: 10px; - cursor: default; - - .tags-delete { - position: absolute; - display: flex; - align-items: center; - justify-content: center; - right: -$delete-height / 2; - top: -$delete-height / 2; - height: $delete-height; - width: $delete-height; - border-radius: 50%; - background-color: $color-red; - transform: rotate(45deg) scale(0.7); - opacity: 0; - transition: opacity 0.2s ease; - cursor: pointer; - } - - &:hover .tags-delete { - opacity: 1; - } - } - - li.tags-add { - height: $add-height; - width: $add-height; - border: none; - text-align: center; - padding: 0; - border-radius: 50%; - background-color: $color-primary-darken; - transform: translateX(-2px) scale(0.7); cursor: pointer; } + + .tags-selected { + background-color: $color-primary-dark; + color: #fff; + } } diff --git a/src/containers/Proxies/components/Group/index.tsx b/src/containers/Proxies/components/Group/index.tsx new file mode 100644 index 0000000..5594ab6 --- /dev/null +++ b/src/containers/Proxies/components/Group/index.tsx @@ -0,0 +1,33 @@ +import * as React from 'react' +import { inject } from 'mobx-react' +import { BaseComponentProps } from '@models' +import { ConfigStore } from '@stores' +import { changeProxySelected, Group as IGroup } from '@lib/request' +import { storeKeys } from '@lib/createStore' +import { Tags } from '@components' +import './style.scss' + +interface GroupProps extends BaseComponentProps { + config: IGroup + store?: ConfigStore +} + +@inject(...storeKeys) +export class Group extends React.Component { + handleChangeProxySelected = async (name: string) => { + await changeProxySelected(this.props.config.name, name) + await this.props.store.fetchData() + } + + render () { + const { config } = this.props + const proxies = new Set(config.all) + return ( +
    + { config.name } + { config.type } + +
    + ) + } +} diff --git a/src/containers/Proxies/components/Group/style.scss b/src/containers/Proxies/components/Group/style.scss new file mode 100644 index 0000000..6cf424b --- /dev/null +++ b/src/containers/Proxies/components/Group/style.scss @@ -0,0 +1,42 @@ +@import '~@styles/variables'; + +.proxy-group { + display: flex; + align-items: center; + font-size: 14px; + padding: 12px 0; + color: $color-black-light; +} + +.proxy-group-name { + display: flex; + padding: 0 20px; + width: 120px; +} + +.proxy-group-type { + height: 24px; + line-height: 24px; + width: 80px; + text-align: center; + background-color: $color-primary-dark; + color: #fff; + border-radius: 3px; +} + +.proxies-group-card { + padding: 0; +} + +.proxies-group-item { + border-bottom: 1px solid $color-gray; + + &:last-child { + border-bottom: none; + } +} + +.proxy-group-tags { + flex: 1; + margin-left: 30px; +} diff --git a/src/containers/Proxies/components/Proxy/index.tsx b/src/containers/Proxies/components/Proxy/index.tsx index 2b4282a..9d44244 100644 --- a/src/containers/Proxies/components/Proxy/index.tsx +++ b/src/containers/Proxies/components/Proxy/index.tsx @@ -1,14 +1,14 @@ import * as React from 'react' import classnames from 'classnames' -import { Icon } from '@components' -import { BaseComponentProps, Proxy as IProxy, TagColors } from '@models' -import { getProxyDelay } from '@lib/request' -import { to, getLocalStorageItem, setLocalStorageItem, sample, noop } from '@lib/helper' +// import { Icon } from '@components' +import { BaseComponentProps, TagColors } from '@models' +import { getProxyDelay, Proxy as IProxy } from '@lib/request' +import { to, getLocalStorageItem, setLocalStorageItem, sample } from '@lib/helper' import './style.scss' interface ProxyProps extends BaseComponentProps { config: IProxy - onEdit?: (e: React.MouseEvent) => void + // onEdit?: (e: React.MouseEvent) => void } interface ProxyState { @@ -18,8 +18,6 @@ interface ProxyState { } export class Proxy extends React.Component { - private mount = true - constructor (props) { super(props) @@ -49,18 +47,10 @@ export class Proxy extends React.Component { } } - componentWillUnmount () { - this.mount = false - } - async componentDidMount () { const { config } = this.props const [res, err] = await to(getProxyDelay(config.name)) - if (!this.mount) { - return - } - if (err) { return this.setState({ hasError: true }) } @@ -70,7 +60,7 @@ export class Proxy extends React.Component { } render () { - const { config, className, onEdit = noop } = this.props + const { config, className } = this.props const { delay, color, hasError } = this.state const backgroundColor = hasError ? undefined : color @@ -79,7 +69,7 @@ export class Proxy extends React.Component { {config.type}

    {config.name}

    {delay === -1 ? '-' : `${delay}ms`}

    - + {/* */} ) } diff --git a/src/containers/Proxies/components/index.ts b/src/containers/Proxies/components/index.ts index dae0b9b..fe4b070 100644 --- a/src/containers/Proxies/components/index.ts +++ b/src/containers/Proxies/components/index.ts @@ -1,2 +1,3 @@ export * from './Proxy' +export * from './Group' export * from './ModifyProxyDialog' diff --git a/src/containers/Proxies/index.tsx b/src/containers/Proxies/index.tsx index bae201e..d566313 100644 --- a/src/containers/Proxies/index.tsx +++ b/src/containers/Proxies/index.tsx @@ -2,79 +2,56 @@ import * as React from 'react' import { translate } from 'react-i18next' import { inject, observer } from 'mobx-react' import { storeKeys } from '@lib/createStore' -import { Header, Icon } from '@components' -import { I18nProps, BaseRouterProps, Proxy as IProxy } from '@models' +import { Card, Header } from '@components' +import { I18nProps, BaseRouterProps } from '@models' -import { Proxy, ModifyProxyDialog } from './components' +import { Proxy, Group } from './components' import './style.scss' interface ProxiesProps extends BaseRouterProps, I18nProps {} interface ProxiesState { - showModifyProxyDialog: boolean - activeConfig?: IProxy - activeConfigIndex?: number } @inject(...storeKeys) @observer class Proxies extends React.Component { - - state = { - showModifyProxyDialog: false, - activeConfig: null, - activeConfigIndex: -1 - } - componentDidMount () { - this.props.config.fetchAndParseConfig() - } - - handleConfigApply = async (config: IProxy) => { - await this.props.config.modifyProxyByIndexAndSave(this.state.activeConfigIndex, config) - this.setState({ showModifyProxyDialog: false, activeConfig: null }) + this.props.store.fetchData() } render () { - const { t, config } = this.props - const { showModifyProxyDialog, activeConfig } = this.state + const { t, store } = this.props return ( - <> -
    -
    -
    - -
    - { - config.config.proxy.length !== 0 &&
      - { - config.config.proxy.map((p, index) => ( -
    • - this.setState({ - showModifyProxyDialog: true, - activeConfig: p, - activeConfigIndex: index - })} /> -
    • - )) - } -
    - } -
    -
    -
    -
    +
    +
    +
    + +
      + { + store.data.proxyGroup.map(p => ( +
    • + +
    • + )) + } +
    +
    - - { - showModifyProxyDialog && this.setState({ showModifyProxyDialog: false, activeConfig: null, activeConfigIndex: -1 })} - /> - } - +
    +
    +
      + { + store.data.proxy.map(p => ( +
    • + +
    • + )) + } +
    +
    +
    ) } } diff --git a/src/containers/Proxies/style.scss b/src/containers/Proxies/style.scss index 20d956c..30b9301 100644 --- a/src/containers/Proxies/style.scss +++ b/src/containers/Proxies/style.scss @@ -15,3 +15,11 @@ } } } + +.proxies-group-list { + list-style: none; +} + +.proxies-group-card { + margin: 15px 0 20px; +} diff --git a/src/containers/Rules/index.tsx b/src/containers/Rules/index.tsx index 4ff552e..1839789 100644 --- a/src/containers/Rules/index.tsx +++ b/src/containers/Rules/index.tsx @@ -13,12 +13,12 @@ interface RulesProps extends BaseRouterProps, I18nProps {} class Rules extends React.Component { async componentDidMount () { - const { config } = this.props - await config.fetchAndParseConfig() + const { store } = this.props + await store.fetchData() } renderRuleItem = ({ index, key, style }) => { - const { rules } = this.props.config.config + const { rules } = this.props.store.data const rule = rules[index] return (
  • @@ -39,7 +39,7 @@ class Rules extends React.Component { render () { const { t } = this.props - const { rules } = this.props.config.config + const { rules } = this.props.store.config return (
    diff --git a/src/containers/Settings/index.tsx b/src/containers/Settings/index.tsx index d7b6e8b..8165eee 100644 --- a/src/containers/Settings/index.tsx +++ b/src/containers/Settings/index.tsx @@ -79,7 +79,7 @@ class Settings extends React.Component { async componentDidMount () { if (isClashX()) { - await rootStores.config.fetchAndParseConfig() + await rootStores.store.fetchAndParseConfig() const startAtLogin = await jsBridge.getStartAtLogin() const setAsSystemProxy = await jsBridge.isSystemProxySet() this.setState({ @@ -88,9 +88,9 @@ class Settings extends React.Component { isClashX: true }) } else { - await rootStores.config.fetchConfig() + await rootStores.store.fetchConfig() } - const general = rootStores.config.config.general + const general = rootStores.store.config.general this.setState({ allowConnectFromLan: general.allowLan, proxyMode: general.mode, diff --git a/src/lib/createStore.ts b/src/lib/createStore.ts index d89506b..5d915ba 100644 --- a/src/lib/createStore.ts +++ b/src/lib/createStore.ts @@ -8,7 +8,7 @@ const history = createHashHistory() export const rootStores = { router: new RouterStore(history), - config: new ConfigStore() + store: new ConfigStore() } export const storeKeys = Object.keys(rootStores) diff --git a/src/lib/helper.ts b/src/lib/helper.ts index fb09a8f..b1d7057 100644 --- a/src/lib/helper.ts +++ b/src/lib/helper.ts @@ -34,3 +34,12 @@ export async function to (promise: Promise): Promise<[T, E]> { } export type Partial = { [P in keyof T]?: T[P] } + +export function partition (arr: T[], fn: (T) => boolean): [T[], T[]] { + const left: T[] = [] + const right: T[] = [] + for (const item of arr) { + fn(item) ? left.push(item) : right.push(item) + } + return [left, right] +} diff --git a/src/lib/request.ts b/src/lib/request.ts index da76902..98f5cdd 100644 --- a/src/lib/request.ts +++ b/src/lib/request.ts @@ -17,19 +17,31 @@ export interface Config { } export interface Rules { - rules: { name: string, payload: string }[] + rules: Rule[] +} + +export interface Rule { + type: string + payload: string + proxy: string } export interface Proxies { proxies: { - [key: string]: Proxy + [key: string]: Proxy | Group } } export interface Proxy { - type: 'Direct' | 'Selector' | 'Reject' | 'URLTest' | 'Shadowsocks' | 'Vmess' | 'Socks' | 'Fallback' - now?: string - all?: string[] + name: string + type: 'Direct' | 'Reject' | 'Shadowsocks' | 'Vmess' | 'Socks' | 'Http' +} + +export interface Group { + name: string + type: 'Selector' | 'URLTest' | 'Fallback' + now: string + all: string[] } export async function getConfig () { @@ -66,7 +78,7 @@ export async function getProxyDelay (name: string) { const req = await getInstance() return req.get<{ delay: number }>(`proxies/${name}/delay`, { params: { - timeout: 20000, + timeout: 5000, url: 'http://www.gstatic.com/generate_204' } }) @@ -74,7 +86,7 @@ export async function getProxyDelay (name: string) { export async function changeProxySelected (name: string, select: string) { const req = await getInstance() - return req.get(`proxies/${name}`, { data: { name: select } }) + return req.put(`proxies/${name}`, { name: select }) } export async function getInstance () { @@ -98,8 +110,8 @@ export async function getInstance () { export async function getExternalControllerConfig () { if (isClashX()) { - await rootStores.config.fetchAndParseConfig() - const general = rootStores.config.config.general + await rootStores.store.fetchAndParseConfig() + const general = rootStores.store.config.general return { hostname: general.externalControllerAddr, diff --git a/src/models/BaseProps.ts b/src/models/BaseProps.ts index 1440187..c2168aa 100644 --- a/src/models/BaseProps.ts +++ b/src/models/BaseProps.ts @@ -14,7 +14,7 @@ export interface BaseRouterProps extends RouteComponentProps, BaseProps {} export interface BaseProps extends BaseComponentProps { styles?: any router?: RouterStore - config?: ConfigStore + store?: ConfigStore } export interface BaseComponentProps { diff --git a/src/models/Config.ts b/src/models/Config.ts index 89fb237..9d111bc 100644 --- a/src/models/Config.ts +++ b/src/models/Config.ts @@ -1,5 +1,6 @@ import { Proxy, ProxyGroup } from './Proxy' import { Rule } from './Rule' +import * as API from '@lib/request' /** * clash config @@ -62,3 +63,45 @@ export interface Config { rules?: Rule[] } + +export interface Data { + + general?: { + + /** + * http proxy port + */ + port?: number + + /** + * socks proxy port + */ + socksPort?: number + + /** + * redir proxy port + */ + redirPort?: number + + /** + * proxy is allow lan + */ + allowLan?: boolean + + /** + * clash proxy mode + */ + mode?: string + + /** + * clash tty log level + */ + logLevel?: string + } + + proxy?: API.Proxy[] + + proxyGroup?: API.Group[] + + rules?: API.Rule[] +} diff --git a/src/stores/ConfigStore.ts b/src/stores/ConfigStore.ts index c698699..a824a7d 100644 --- a/src/stores/ConfigStore.ts +++ b/src/stores/ConfigStore.ts @@ -2,8 +2,8 @@ import { observable, action, runInAction } from 'mobx' import * as yaml from 'yaml' import * as Models from '@models' import { jsBridge } from '@lib/jsBridge' -import { getConfig } from '@lib/request' -import { getLocalStorageItem } from '@lib/helper' +import * as API from '@lib/request' +import { getLocalStorageItem, partition } from '@lib/helper' export class ConfigStore { @@ -15,19 +15,47 @@ export class ConfigStore { } @observable - public state: 'pending' | 'ok' | 'error' = 'pending' + data: Models.Data = { + general: {}, + proxy: [], + proxyGroup: [], + rules: [] + } + + @action + async fetchData () { + const [{ data: general }, rawProxies, rules] = await Promise.all([API.getConfig(), API.getProxies(), API.getRules()]) + + runInAction(() => { + this.data.general = { + port: general.port, + socksPort: general['socket-port'], + redirPort: general['redir-port'], + mode: general.mode, + logLevel: general['log-level'] + } + + const policyGroup = new Set(['Selector', 'URLTest', 'Fallback']) + const unUsedProxy = new Set(['DIRECT', 'REJECT', 'GLOBAL']) + const proxies = Object.keys(rawProxies.data.proxies) + .filter(key => !unUsedProxy.has(key)) + .map(key => ({ ...rawProxies.data.proxies[key], name: key })) + const [proxy, groups] = partition(proxies, proxy => !policyGroup.has(proxy.type)) + this.data.proxy = proxy as API.Proxy[] + this.data.proxyGroup = groups as API.Group[] + + this.data.rules = rules.data.rules + }) + } @action async fetchAndParseConfig () { - this.state = 'pending' - const rawConfig = await jsBridge.readConfigString() runInAction(() => { // emit error when config is empty // because read config might be error if (!rawConfig) { - this.state = 'error' return } @@ -65,13 +93,12 @@ export class ConfigStore { proxyGroup, rules: rule || [] } - this.state = 'ok' }) } @action async fetchConfig () { - const { data: config } = await getConfig() + const { data: config } = await API.getConfig() this.config = { general: { port: config.port, diff --git a/src/styles/variables.scss b/src/styles/variables.scss index 0c5da65..72cee81 100644 --- a/src/styles/variables.scss +++ b/src/styles/variables.scss @@ -18,4 +18,5 @@ $color-white: #fff; $color-green: #67c23a; $color-orange: #e6a23c; $color-red: #f56c6c; +$color-black-light: #546b87; $color-black: #000;