Skip to content

Commit f645131

Browse files
authored
v1.20.0 (#80)
* feat(Home): advanced subscription config * feat(Home): support editing subscriptions * chore: v1.20.0-beat.0 * refactor: rename SubscriptionUrlModal to SubscriptionModal * chore: upgrade deps * chore: upgrade deps * chore: v1.20.0-beat.1 * fix: call setState in effect * fix: fail to update proxies of subscription * refactor: update url of subscription * fix: fail to update proxies of subscription * fix: fail to update proxies of subscription * chore: v1.20.0-beat.2 * fix: invalid selected proxy after updating subscription * feat: update i18n text * chore: v1.20.0-beat.3 * feat: allow to delete unselected cards when Lux is disconnected * feat: allow to delete unselected cards when Lux is disconnected * chore: v1.20.0-beat.4 * chore: v1.20.0
1 parent 4fababc commit f645131

29 files changed

Lines changed: 1836 additions & 1379 deletions

File tree

modules/lux-js-sdk/proxy.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@ import {
44
type AddProxy,
55
type DeleteAllProxies,
66
type DeleteProxies,
7+
DeleteSubscription,
78
type GetCurProxy,
89
type GetProxies,
910
type GetProxyDelay,
1011
type GetResFromUrl,
12+
GetSubscriptions,
1113
type TestProxyUdp,
1214
type UpdateProxy,
15+
UpdateSubscription,
16+
UpdateSubscriptionProxies,
1317
} from "./types";
1418
import { urtConfig } from "./url";
1519

@@ -40,7 +44,7 @@ export const addProxy: AddProxy = async (req) => {
4044

4145
export const addProxiesFromSubscriptionUrl: AddProxiesFromSubscriptionUrl =
4246
async (req) => {
43-
const url = `${urtConfig.proxies}/subscription-url`;
47+
const url = `${urtConfig.subscription}`;
4448
const res = await axios.put(url, req);
4549
return res.data;
4650
};
@@ -72,7 +76,33 @@ export const testProxyUdp: TestProxyUdp = async (req) => {
7276

7377
export const getResFromUrl: GetResFromUrl = async (req) => {
7478
const { url: targetUrl } = req;
75-
const url = `${urtConfig.proxies}/url`;
79+
const url = `${urtConfig.subscription}/url`;
7680
const res = await axios.post(url, { url: targetUrl });
7781
return res.data;
7882
};
83+
84+
export const getSubscriptions: GetSubscriptions = async () => {
85+
const url = `${urtConfig.subscription}/all`;
86+
const res = await axios.get(url);
87+
return res.data;
88+
};
89+
90+
export const deleteSubscription: DeleteSubscription = async (req) => {
91+
const url = `${urtConfig.subscription}`;
92+
const res = await axios.delete(url, { data: req });
93+
return res.data;
94+
};
95+
96+
export const updateSubscription: UpdateSubscription = async (req) => {
97+
const url = `${urtConfig.subscription}`;
98+
const res = await axios.post(url, req);
99+
return res.data;
100+
};
101+
102+
export const updateSubscriptionProxies: UpdateSubscriptionProxies = async (
103+
req,
104+
) => {
105+
const url = `${urtConfig.subscription}/proxies`;
106+
const res = await axios.post(url, req);
107+
return res.data;
108+
};

modules/lux-js-sdk/types/proxy/api.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { type BaseProxy } from "./base";
2+
import { Subscription } from "./subscription";
23

34
export interface GetProxiesRes {
45
proxies: BaseProxy[];
@@ -28,11 +29,13 @@ export type AddProxy = (req: AddProxyReq) => Promise<{ id: string }>;
2829
interface addProxiesFromSubscriptionUrlReq {
2930
proxies: Array<Omit<BaseProxy, "id">>;
3031
subscriptionUrl: string;
32+
subscriptionName: string;
33+
subscriptionRemark: string;
3134
}
3235

3336
export type AddProxiesFromSubscriptionUrl = (
3437
req: addProxiesFromSubscriptionUrlReq,
35-
) => Promise<{ proxies: BaseProxy[] }>;
38+
) => Promise<{ proxies: BaseProxy[]; subscriptions: Subscription[] }>;
3639

3740
interface DeleteProxiesReq {
3841
ids: string[];
@@ -68,3 +71,33 @@ interface GetResFromUrlReq {
6871
export type GetResFromUrl = (
6972
req: GetResFromUrlReq,
7073
) => Promise<{ data: string }>;
74+
75+
export interface GetSubscriptionsRes {
76+
subscriptions: Subscription[];
77+
}
78+
export type GetSubscriptions = () => Promise<GetSubscriptionsRes>;
79+
80+
export interface DeleteSubscriptionReq {
81+
id: string;
82+
}
83+
84+
export type DeleteSubscription = (req: DeleteSubscriptionReq) => Promise<void>;
85+
86+
export interface UpdateSubscriptionReq {
87+
subscription: Subscription;
88+
}
89+
90+
export type UpdateSubscription = (req: UpdateSubscriptionReq) => Promise<void>;
91+
92+
export interface UpdateSubscriptionProxiesReq {
93+
subscriptionId: string;
94+
proxies: Omit<BaseProxy, "id">[];
95+
}
96+
97+
export interface UpdateSubscriptionProxiesRes {
98+
proxies: BaseProxy[];
99+
}
100+
101+
export type UpdateSubscriptionProxies = (
102+
req: UpdateSubscriptionProxiesReq,
103+
) => Promise<UpdateSubscriptionProxiesRes>;

modules/lux-js-sdk/types/proxy/base.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export interface CommonProxy {
1111
name: string;
1212
delay?: number;
1313
subscriptionUrl?: string;
14+
subscription?: string;
1415
}
1516

1617
export interface BaseProxy extends IProxyConfig, CommonProxy {}

modules/lux-js-sdk/types/proxy/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export * from "./http";
44
export * from "./plugin";
55
export * from "./shadowsocks";
66
export * from "./socks5";
7+
export * from "./subscription";
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export interface Subscription {
2+
id: string;
3+
url: string;
4+
name: string;
5+
remark: string;
6+
}

modules/lux-js-sdk/url.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ class UrlConfig {
7474
get event() {
7575
return `${this.wsPrefix}${this.baseUrl}/event`;
7676
}
77+
78+
get subscription() {
79+
return `${this.httpPrefix}${this.baseUrl}/subscription`;
80+
}
7781
}
7882

7983
export const urtConfig = new UrlConfig();

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "lux",
3-
"version": "1.19.3",
3+
"version": "1.20.0",
44
"description": "A system network proxy tool",
55
"main": "src/index.js",
66
"scripts": {
@@ -12,8 +12,8 @@
1212
"author": "igoogolx",
1313
"license": "MIT",
1414
"dependencies": {
15-
"@fluentui-contrib/react-data-grid-react-window": "^1.4.1",
16-
"@fluentui/react-components": "^9.72.4",
15+
"@fluentui-contrib/react-data-grid-react-window": "^1.4.2",
16+
"@fluentui/react-components": "^9.72.8",
1717
"@fortawesome/fontawesome-free": "^7.0.0",
1818
"@reduxjs/toolkit": "^2.9.0",
1919
"@tailwindcss/postcss": "^4.1.16",
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import {
2+
Field as FluentInput,
3+
type InputProps,
4+
Textarea,
5+
} from "@fluentui/react-components";
6+
import { useField } from "formik";
7+
import React from "react";
8+
9+
export interface TextareaFieldProps<T extends string> {
10+
name: T;
11+
validate?: (value: string) => string;
12+
label?: string;
13+
type?: InputProps["type"];
14+
adornment?: InputProps["contentBefore"];
15+
disabled?: boolean;
16+
className?: string;
17+
}
18+
19+
export function TextareaField<T extends string>(
20+
props: Readonly<TextareaFieldProps<T>>,
21+
) {
22+
const { name, label, validate, disabled = false, className } = props;
23+
const [field, meta] = useField({ name, validate });
24+
return (
25+
<FluentInput
26+
label={label}
27+
validationMessage={meta.error && meta.touched ? meta.error : null}
28+
className={className}
29+
spellCheck={false}
30+
>
31+
<Textarea {...field} disabled={disabled} />
32+
</FluentInput>
33+
);
34+
}

src/components/Modal/Proxy/SubscriptionUrlModal/index.module.css renamed to src/components/Modal/Proxy/SubscriptionModal/index.module.css

File renamed without changes.
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import { Field, Form, notifier } from "@/components/Core";
2+
import { TextareaField } from "@/components/Core/Form/TextareaField";
3+
import { useProxySubscription } from "@/hooks";
4+
import { TRANSLATION_KEY } from "@/i18n/locales/key";
5+
import { proxiesSlice, type RootState, subscriptionsSlice } from "@/reducers";
6+
import { formatError } from "@/utils/error";
7+
import { decodeFromUrl } from "@/utils/url";
8+
import { Button, Spinner } from "@fluentui/react-components";
9+
import axios from "axios";
10+
import {
11+
addProxiesFromSubscriptionUrl,
12+
Subscription,
13+
updateSubscription,
14+
} from "lux-js-sdk";
15+
import React, { useState } from "react";
16+
import { useTranslation } from "react-i18next";
17+
import { useDispatch, useSelector } from "react-redux";
18+
import * as Yup from "yup";
19+
import styles from "./index.module.css";
20+
21+
interface SubscriptionModalProps {
22+
close: () => void;
23+
initialValue?: Subscription;
24+
isSelected?: boolean;
25+
}
26+
27+
const INIT_DATA: Subscription = {
28+
id: "",
29+
name: "",
30+
url: "",
31+
remark: "",
32+
};
33+
34+
function SubscriptionModal(props: Readonly<SubscriptionModalProps>) {
35+
const { close, initialValue, isSelected } = props;
36+
const { t } = useTranslation();
37+
38+
const SubscriptionSchema = Yup.object().shape({
39+
url: Yup.string().required(t(TRANSLATION_KEY.REQUIRED)),
40+
name: Yup.string(),
41+
remark: Yup.string(),
42+
});
43+
44+
const [loading, setLoading] = useState(false);
45+
const dispatch = useDispatch();
46+
47+
const isStarted = useSelector<RootState, boolean>(
48+
(state) => state.manager.isStared,
49+
);
50+
const { update: updateSubscriptionProxies } = useProxySubscription();
51+
52+
const onSubmit = async (data: Subscription) => {
53+
try {
54+
setLoading(true);
55+
if (!data.url) {
56+
return;
57+
}
58+
59+
try {
60+
if (data.id) {
61+
await updateSubscription({ subscription: data });
62+
dispatch(
63+
subscriptionsSlice.actions.updateOne({ subscription: data }),
64+
);
65+
if (initialValue?.url !== data.url) {
66+
await updateSubscriptionProxies(data.id, data.url);
67+
}
68+
} else {
69+
const decodedProxies = await decodeFromUrl(data.url);
70+
const res = await addProxiesFromSubscriptionUrl({
71+
proxies: decodedProxies,
72+
subscriptionUrl: data.url,
73+
subscriptionName: data.name,
74+
subscriptionRemark: data.remark,
75+
});
76+
dispatch(proxiesSlice.actions.received({ proxies: res.proxies }));
77+
dispatch(
78+
subscriptionsSlice.actions.received({
79+
subscriptions: res.subscriptions,
80+
}),
81+
);
82+
}
83+
close();
84+
notifier.success(t(TRANSLATION_KEY.UPDATE_SUCCESS));
85+
} catch (e) {
86+
if (!axios.isAxiosError(e)) {
87+
notifier.error(formatError(e));
88+
}
89+
}
90+
} finally {
91+
setLoading(false);
92+
}
93+
};
94+
return (
95+
<Form
96+
onSubmit={onSubmit}
97+
initialValues={initialValue ?? INIT_DATA}
98+
validationSchema={SubscriptionSchema}
99+
>
100+
{({ isValid, submitForm }) => {
101+
return (
102+
<>
103+
<TextareaField<keyof Subscription>
104+
name="url"
105+
label={t(TRANSLATION_KEY.URL)}
106+
/>
107+
<Field<keyof Subscription>
108+
name="name"
109+
label={`${t(TRANSLATION_KEY.FORM_NAME)}(${t(
110+
TRANSLATION_KEY.FORM_OPTIONAL,
111+
)})`}
112+
/>
113+
<Field<keyof Subscription>
114+
name="remark"
115+
label={`${t(TRANSLATION_KEY.REMARK)}(${t(
116+
TRANSLATION_KEY.FORM_OPTIONAL,
117+
)})`}
118+
/>
119+
<div className={styles.buttonContainer}>
120+
<Button onClick={close} className={styles.button}>
121+
{t(TRANSLATION_KEY.FORM_CANCEL)}
122+
</Button>
123+
<Button
124+
className={styles.button}
125+
disabled={!isValid || (isSelected && isStarted) || loading}
126+
onClick={submitForm}
127+
appearance="primary"
128+
>
129+
{loading && (
130+
<Spinner size="extra-tiny" className={styles.spinner} />
131+
)}
132+
{t(TRANSLATION_KEY.FORM_SAVE)}
133+
</Button>
134+
</div>
135+
</>
136+
);
137+
}}
138+
</Form>
139+
);
140+
}
141+
142+
export default SubscriptionModal;

0 commit comments

Comments
 (0)