From be211882084ed04207ec296e71b335daeb7fbf3f Mon Sep 17 00:00:00 2001 From: Tao Date: Fri, 15 Dec 2023 21:14:42 +0800 Subject: [PATCH] feat: grab proxy-groups names from config --- .../ui/src/page/current-config/ConfigDND.tsx | 9 +- .../page/library-rule-list/AddRuleModal.tsx | 124 ++++++++++++------ .../ui/src/page/library-rule-list/index.tsx | 4 +- packages/ui/src/util/gen.ts | 15 ++- 4 files changed, 98 insertions(+), 54 deletions(-) diff --git a/packages/ui/src/page/current-config/ConfigDND.tsx b/packages/ui/src/page/current-config/ConfigDND.tsx index 80d1e89..c58f41a 100644 --- a/packages/ui/src/page/current-config/ConfigDND.tsx +++ b/packages/ui/src/page/current-config/ConfigDND.tsx @@ -1,6 +1,5 @@ import { ConfigItem } from '$ui/define' import { cx } from '$ui/libs' -import { rootState } from '$ui/store' import { limitLines } from '$ui/util/text-util' import { InfoCircleOutlined, QuestionCircleFilled } from '@ant-design/icons' import { useMemoizedFn } from 'ahooks' @@ -9,6 +8,8 @@ import debugFactory from 'debug' import { useEffect, useMemo, useState } from 'react' import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd' import { useSnapshot } from 'valtio' +import { state as libraryRuleListState } from '../library-rule-list/model' +import { state as librarySubscribeState } from '../library-subscribe/model' import styles from './ConfigDND.module.less' import { state } from './model' @@ -16,9 +17,7 @@ const dndDebug = debugFactory('app:page:current-config:ConfigDND') export function ConfigDND() { // subscribe - const rootStateSnap = useSnapshot(rootState) - - const subscribeList = rootStateSnap.librarySubscribe.list + const subscribeList = useSnapshot(librarySubscribeState.list) const subscribeSourceList = useMemo(() => { return subscribeList.map((item) => { return { @@ -31,7 +30,7 @@ export function ConfigDND() { }, [subscribeList]) // rule - const ruleList = rootStateSnap.libraryRuleList.list + const ruleList = useSnapshot(libraryRuleListState.list) const ruleSourceList = useMemo(() => { return ruleList.map((item) => { return { diff --git a/packages/ui/src/page/library-rule-list/AddRuleModal.tsx b/packages/ui/src/page/library-rule-list/AddRuleModal.tsx index b2c3239..f5288a3 100644 --- a/packages/ui/src/page/library-rule-list/AddRuleModal.tsx +++ b/packages/ui/src/page/library-rule-list/AddRuleModal.tsx @@ -1,10 +1,12 @@ import { message } from '$ui/store' +import { genConfig } from '$ui/util/gen' import { css } from '@emotion/react' import { useMemoizedFn, useUpdateEffect } from 'ahooks' import { AutoComplete, Button, Col, Input, Modal, Row, Select, Space } from 'antd' import AppleScript from 'applescript' import { clipboard } from 'electron' import Yaml from 'js-yaml' +import { uniq } from 'lodash' import pify from 'promise.ify' import { useCallback, useEffect, useMemo, useState } from 'react' import { tldExists } from 'tldjs' @@ -18,10 +20,10 @@ const { Option } = Select // from-global: 从主页中直接打开 export type Mode = 'from-rule' | 'from-global' -type ClashRuleType = 'DOMAIN-SUFFIX' | 'DOMAIN-KEYWORD' -const TYPES: ClashRuleType[] = ['DOMAIN-SUFFIX', 'DOMAIN-KEYWORD'] +type ClashRuleType = 'DOMAIN-SUFFIX' | 'DOMAIN-KEYWORD' | 'DOMAIN' +const TYPES: ClashRuleType[] = ['DOMAIN-SUFFIX', 'DOMAIN-KEYWORD', 'DOMAIN'] -const TARGETS = ['Proxy', 'DIRECT', 'REJECT'] +const DEFAULT_TARGETS = ['Proxy', 'DIRECT', 'REJECT'] interface IProps { visible: boolean @@ -70,6 +72,22 @@ export default function AddRuleModal(props: IProps) { setVisible(false) }) + /** + * rule detail + */ + + const [type, setType] = useState(TYPES[0]) + const [url, setUrl] = useState('') + const [target, setTarget] = useState(DEFAULT_TARGETS[0]) + + const [extraTargets, setExtraTargets] = useState([]) + const [targetAutoCompleteList, setTargetAutoCompleteList] = useState([]) + + const allTargets = useMemo( + () => uniq([target, ...DEFAULT_TARGETS, ...extraTargets].filter(Boolean)), + [target, extraTargets], + ) + /** * source url */ @@ -78,6 +96,7 @@ export default function AddRuleModal(props: IProps) { const [autoCompletes, setAutoCompletes] = useState>(() => ({ 'DOMAIN-KEYWORD': [], 'DOMAIN-SUFFIX': [], + 'DOMAIN': [], })) const changeProcessUrl = useMemoizedFn((url: string) => { @@ -93,14 +112,14 @@ export default function AddRuleModal(props: IProps) { setAutoCompletes(data) }) - const readClipboardUrl = useCallback(() => { + const useClipboardUrl = useCallback(() => { const url = clipboard.readText() if (url) { changeProcessUrl(url) } }, []) - const readChromeUrl = useCallback(async () => { + const useChromeUrl = useCallback(async () => { const url = await getChromeUrl() if (url) { message.success('获取 chrome url 成功') @@ -108,20 +127,50 @@ export default function AddRuleModal(props: IProps) { } }, []) + const updateExtraTargets = useMemoizedFn(async () => { + const config = await genConfig() + const proxyGroupNames = (config['proxy-groups'] || []).map((x) => x.name) + setExtraTargets(proxyGroupNames) + await new Promise((resolve) => setTimeout(resolve)) + resetTargetAutoCompleteList() + }) + + const resetTargetAutoCompleteList = useMemoizedFn(() => { + setTargetAutoCompleteList(allTargets) + }) + // default use chrome url useUpdateEffect(() => { if (visible) { - readChromeUrl() + useChromeUrl() + updateExtraTargets() } }, [visible]) - /** - * rule detail - */ + const onSearchTargets = useMemoizedFn((text: string) => { + // TODO: add fuzzy search - const [type, setType] = useState(TYPES[0]) - const [url, setUrl] = useState('') - const [target, setTarget] = useState(TARGETS[0]) + if (!text) { + setTargetAutoCompleteList([]) + return + } + + const _text = text + text = text.toLowerCase() + const searchFrom = uniq(extraTargets) + const filtered = uniq([ + _text, + ...DEFAULT_TARGETS, + ...searchFrom.filter((name) => { + return name.toLowerCase().startsWith(text) + }), + ...searchFrom.filter((name) => { + return name.toLowerCase().includes(text) + }), + ]) + + setTargetAutoCompleteList(filtered) + }) const curAutoCompletes = useMemo(() => { return autoCompletes[type] || [] @@ -137,7 +186,7 @@ export default function AddRuleModal(props: IProps) { * ui style */ - const layout = [{ span: 7 }, { flex: 1 }, { span: 4 }] + const layout = [{ span: 6 }, { flex: 1 }, { span: 6 }] const okButtonProps = useMemo(() => { const disabled = !(type && target && url) @@ -168,8 +217,8 @@ export default function AddRuleModal(props: IProps) { />
- - + @@ -193,33 +242,24 @@ export default function AddRuleModal(props: IProps) { - - {url && !curAutoCompletes.includes(url) && ( - - )} - {curAutoCompletes.map((t) => ( - - ))} - + ({ value }))} + /> - - {!TARGETS.includes(target) && ( - - )} - {TARGETS.map((t) => ( - - ))} - + ({ value }))} + onSearch={onSearchTargets} + onSelect={resetTargetAutoCompleteList} + allowClear + /> @@ -330,12 +370,9 @@ async function getChromeUrl() { return url } -// FIXME -// global.URI = URI - function getAutoCompletes(url: string) { const u = new URI(url) - // const fullDomain = u.domain() // full domain, e.g www.githug.com + // const fullDomain = u.domain() // full domain, e.g www.github.com // const shortDomain = u.domain(true) // without subdomain, e.g github.com // const keyword = shortDomain.split('.')[0] // e.g github @@ -360,5 +397,6 @@ function getAutoCompletes(url: string) { return { 'DOMAIN-KEYWORD': keywords, 'DOMAIN-SUFFIX': suffixes, + 'DOMAIN': [hostname], } } diff --git a/packages/ui/src/page/library-rule-list/index.tsx b/packages/ui/src/page/library-rule-list/index.tsx index cf0dfa0..eab8119 100644 --- a/packages/ui/src/page/library-rule-list/index.tsx +++ b/packages/ui/src/page/library-rule-list/index.tsx @@ -323,9 +323,9 @@ export function PartialConfigItem({ item, index }: { item: RuleItem; index: numb >
diff --git a/packages/ui/src/util/gen.ts b/packages/ui/src/util/gen.ts index 7f35da0..c78d543 100644 --- a/packages/ui/src/util/gen.ts +++ b/packages/ui/src/util/gen.ts @@ -50,14 +50,12 @@ export function getUsingItems() { return { subscribeItems, ruleItems } } -export default async function genConfig({ forceUpdate = false }: { forceUpdate?: boolean } = {}) { +export async function genConfig({ forceUpdate = false }: { forceUpdate?: boolean } = {}) { const { name, clashMeta, generateAllProxyGroup, generateSubNameProxyGroup } = rootState.currentConfig const { subscribeItems, ruleItems } = getUsingItems() - /** - * config merge - */ + // the config let config: Partial = {} // 值为 array 的 key 集合 @@ -301,6 +299,15 @@ export default async function genConfig({ forceUpdate = false }: { forceUpdate?: } /* #endregion */ + return config +} + +export default async function genConfigThenWrite({ + forceUpdate = false, +}: { forceUpdate?: boolean } = {}) { + const config = await genConfig({ forceUpdate }) + + const { name, clashMeta } = rootState.currentConfig const configYaml = YAML.dump(config) const file = getConfigFile(name, clashMeta)