summaryrefslogtreecommitdiff
path: root/src/components/BackendList.tsx
diff options
context:
space:
mode:
authorHaishan <[email protected]>2020-09-13 16:34:18 +0800
committerHaishan <[email protected]>2020-09-13 17:16:14 +0800
commit15bc0f69a8367a57fa1bf263e615285349ad4ab9 (patch)
treefbdd2a46303703822f7e7bc3462a70b4855fe4a1 /src/components/BackendList.tsx
parenta8f0d3d4b4928caebf61c75fa9191a170b471035 (diff)
feat: multi backends management
Diffstat (limited to 'src/components/BackendList.tsx')
-rw-r--r--src/components/BackendList.tsx147
1 files changed, 147 insertions, 0 deletions
diff --git a/src/components/BackendList.tsx b/src/components/BackendList.tsx
new file mode 100644
index 0000000..a0c993f
--- /dev/null
+++ b/src/components/BackendList.tsx
@@ -0,0 +1,147 @@
+import cx from 'clsx';
+import * as React from 'react';
+import { Eye, EyeOff, X as Close } from 'react-feather';
+import { useToggle } from 'src/hooks/basic';
+import {
+ getClashAPIConfigs,
+ getSelectedClashAPIConfigIndex,
+} from 'src/store/app';
+import { ClashAPIConfig } from 'src/types';
+
+import s from './BackendList.module.css';
+import { connect, useStoreActions } from './StateProvider';
+
+type Config = ClashAPIConfig & { addedAt: number };
+
+const mapState = (s) => ({
+ apiConfigs: getClashAPIConfigs(s),
+ selectedClashAPIConfigIndex: getSelectedClashAPIConfigIndex(s),
+});
+
+export const BackendList = connect(mapState)(BackendListImpl);
+
+function BackendListImpl({
+ apiConfigs,
+ selectedClashAPIConfigIndex,
+}: {
+ apiConfigs: Config[];
+ selectedClashAPIConfigIndex: number;
+}) {
+ const {
+ app: { removeClashAPIConfig, selectClashAPIConfig },
+ } = useStoreActions();
+
+ const onRemove = React.useCallback(
+ (conf: ClashAPIConfig) => {
+ removeClashAPIConfig(conf);
+ },
+ [removeClashAPIConfig]
+ );
+ const onSelect = React.useCallback(
+ (conf: ClashAPIConfig) => {
+ selectClashAPIConfig(conf);
+ },
+ [selectClashAPIConfig]
+ );
+
+ return (
+ <>
+ <ul className={s.ul}>
+ {apiConfigs.map((item, idx) => {
+ return (
+ <li
+ className={cx(s.li, {
+ [s.hasSecret]: item.secret,
+ [s.isSelected]: idx === selectedClashAPIConfigIndex,
+ })}
+ key={item.baseURL + item.secret}
+ >
+ <Item
+ disableRemove={idx === selectedClashAPIConfigIndex}
+ baseURL={item.baseURL}
+ secret={item.secret}
+ onRemove={onRemove}
+ onSelect={onSelect}
+ />
+ </li>
+ );
+ })}
+ </ul>
+ </>
+ );
+}
+
+function Item({
+ baseURL,
+ secret,
+ disableRemove,
+ onRemove,
+ onSelect,
+}: {
+ baseURL: string;
+ secret: string;
+ disableRemove: boolean;
+ onRemove: (x: ClashAPIConfig) => void;
+ onSelect: (x: ClashAPIConfig) => void;
+}) {
+ const [show, toggle] = useToggle();
+ const Icon = show ? EyeOff : Eye;
+
+ const handleTap = React.useCallback((e: React.KeyboardEvent) => {
+ e.stopPropagation();
+ }, []);
+
+ return (
+ <>
+ <Button
+ disabled={disableRemove}
+ onClick={() => onRemove({ baseURL, secret })}
+ className={s.close}
+ >
+ <Close size={20} />
+ </Button>
+ <span
+ className={s.url}
+ tabIndex={0}
+ role="button"
+ onClick={() => onSelect({ baseURL, secret })}
+ onKeyUp={handleTap}
+ >
+ {baseURL}
+ </span>
+ <span />
+ {secret ? (
+ <>
+ <span className={s.secret}>{show ? secret : '***'}</span>
+
+ <Button onClick={toggle} className={s.eye}>
+ <Icon size={20} />
+ </Button>
+ </>
+ ) : null}
+ </>
+ );
+}
+
+function Button({
+ children,
+ onClick,
+ className,
+ disabled,
+}: {
+ children: React.ReactNode;
+
+ onClick?: (e: React.MouseEvent<HTMLButtonElement>) => unknown;
+ className: string;
+ disabled?: boolean;
+}) {
+ return (
+ <button
+ disabled={disabled}
+ className={cx(className, s.btn)}
+ onClick={onClick}
+ >
+ {children}
+ </button>
+ );
+}