feat: profile item adjust
This commit is contained in:
parent
08fa5205b0
commit
f44039b628
@ -32,7 +32,7 @@ pub async fn import_profile(
|
|||||||
with_proxy: bool,
|
with_proxy: bool,
|
||||||
profiles_state: State<'_, ProfilesState>,
|
profiles_state: State<'_, ProfilesState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let item = wrap_err!(PrfItem::from_url(&url, with_proxy).await)?;
|
let item = wrap_err!(PrfItem::from_url(&url, None, None, with_proxy).await)?;
|
||||||
|
|
||||||
let mut profiles = profiles_state.0.lock().unwrap();
|
let mut profiles = profiles_state.0.lock().unwrap();
|
||||||
wrap_err!(profiles.append_item(item))
|
wrap_err!(profiles.append_item(item))
|
||||||
@ -42,12 +42,11 @@ pub async fn import_profile(
|
|||||||
/// append a temp profile item file to the `profiles` dir
|
/// append a temp profile item file to the `profiles` dir
|
||||||
/// view the temp profile file by using vscode or other editor
|
/// view the temp profile file by using vscode or other editor
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn new_profile(
|
pub async fn create_profile(
|
||||||
name: String,
|
item: PrfItem, // partial
|
||||||
desc: String,
|
|
||||||
profiles_state: State<'_, ProfilesState>,
|
profiles_state: State<'_, ProfilesState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let item = wrap_err!(PrfItem::from_local(name, desc))?;
|
let item = wrap_err!(PrfItem::from(item).await)?;
|
||||||
let mut profiles = profiles_state.0.lock().unwrap();
|
let mut profiles = profiles_state.0.lock().unwrap();
|
||||||
|
|
||||||
wrap_err!(profiles.append_item(item))
|
wrap_err!(profiles.append_item(item))
|
||||||
@ -73,7 +72,7 @@ pub async fn update_profile(
|
|||||||
item.url.clone().unwrap()
|
item.url.clone().unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
let item = wrap_err!(PrfItem::from_url(&url, with_proxy).await)?;
|
let item = wrap_err!(PrfItem::from_url(&url, None, None, with_proxy).await)?;
|
||||||
|
|
||||||
let mut profiles = profiles_state.0.lock().unwrap();
|
let mut profiles = profiles_state.0.lock().unwrap();
|
||||||
wrap_err!(profiles.update_item(index.clone(), item))?;
|
wrap_err!(profiles.update_item(index.clone(), item))?;
|
||||||
|
@ -75,6 +75,42 @@ impl Default for PrfItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PrfItem {
|
impl PrfItem {
|
||||||
|
/// From partial item
|
||||||
|
/// must contain `itype`
|
||||||
|
pub async fn from(item: PrfItem) -> Result<PrfItem> {
|
||||||
|
if item.itype.is_none() {
|
||||||
|
bail!("type should not be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
match item.itype.unwrap().as_str() {
|
||||||
|
"remote" => {
|
||||||
|
if item.url.is_none() {
|
||||||
|
bail!("url should not be null");
|
||||||
|
}
|
||||||
|
let url = item.url.as_ref().unwrap().as_str();
|
||||||
|
let name = item.name;
|
||||||
|
let desc = item.desc;
|
||||||
|
PrfItem::from_url(url, name, desc, false).await
|
||||||
|
}
|
||||||
|
"local" => {
|
||||||
|
let name = item.name.unwrap_or("Local File".into());
|
||||||
|
let desc = item.desc.unwrap_or("".into());
|
||||||
|
PrfItem::from_local(name, desc)
|
||||||
|
}
|
||||||
|
"merge" => {
|
||||||
|
let name = item.name.unwrap_or("Merge".into());
|
||||||
|
let desc = item.desc.unwrap_or("".into());
|
||||||
|
PrfItem::from_merge(name, desc)
|
||||||
|
}
|
||||||
|
"script" => {
|
||||||
|
let name = item.name.unwrap_or("Script".into());
|
||||||
|
let desc = item.desc.unwrap_or("".into());
|
||||||
|
PrfItem::from_script(name, desc)
|
||||||
|
}
|
||||||
|
typ @ _ => bail!("invalid type \"{typ}\""),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// ## Local type
|
/// ## Local type
|
||||||
/// create a new item from name/desc
|
/// create a new item from name/desc
|
||||||
pub fn from_local(name: String, desc: String) -> Result<PrfItem> {
|
pub fn from_local(name: String, desc: String) -> Result<PrfItem> {
|
||||||
@ -91,13 +127,18 @@ impl PrfItem {
|
|||||||
selected: None,
|
selected: None,
|
||||||
extra: None,
|
extra: None,
|
||||||
updated: Some(help::get_now()),
|
updated: Some(help::get_now()),
|
||||||
file_data: Some(tmpl::ITEM_CONFIG.into()),
|
file_data: Some(tmpl::ITEM_LOCAL.into()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ## Remote type
|
/// ## Remote type
|
||||||
/// create a new item from url
|
/// create a new item from url
|
||||||
pub async fn from_url(url: &str, with_proxy: bool) -> Result<PrfItem> {
|
pub async fn from_url(
|
||||||
|
url: &str,
|
||||||
|
name: Option<String>,
|
||||||
|
desc: Option<String>,
|
||||||
|
with_proxy: bool,
|
||||||
|
) -> Result<PrfItem> {
|
||||||
let mut builder = reqwest::ClientBuilder::new();
|
let mut builder = reqwest::ClientBuilder::new();
|
||||||
|
|
||||||
if !with_proxy {
|
if !with_proxy {
|
||||||
@ -124,14 +165,14 @@ impl PrfItem {
|
|||||||
|
|
||||||
let uid = help::get_uid("r");
|
let uid = help::get_uid("r");
|
||||||
let file = format!("{uid}.yaml");
|
let file = format!("{uid}.yaml");
|
||||||
let name = uid.clone();
|
let name = name.unwrap_or(uid.clone());
|
||||||
let data = resp.text_with_charset("utf-8").await?;
|
let data = resp.text_with_charset("utf-8").await?;
|
||||||
|
|
||||||
Ok(PrfItem {
|
Ok(PrfItem {
|
||||||
uid: Some(uid),
|
uid: Some(uid),
|
||||||
itype: Some("remote".into()),
|
itype: Some("remote".into()),
|
||||||
name: Some(name),
|
name: Some(name),
|
||||||
desc: None,
|
desc,
|
||||||
file: Some(file),
|
file: Some(file),
|
||||||
url: Some(url.into()),
|
url: Some(url.into()),
|
||||||
selected: None,
|
selected: None,
|
||||||
@ -140,6 +181,46 @@ impl PrfItem {
|
|||||||
file_data: Some(data),
|
file_data: Some(data),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ## Merge type (enhance)
|
||||||
|
/// create the enhanced item by using `merge` rule
|
||||||
|
pub fn from_merge(name: String, desc: String) -> Result<PrfItem> {
|
||||||
|
let uid = help::get_uid("m");
|
||||||
|
let file = format!("{uid}.yaml");
|
||||||
|
|
||||||
|
Ok(PrfItem {
|
||||||
|
uid: Some(uid),
|
||||||
|
itype: Some("merge".into()),
|
||||||
|
name: Some(name),
|
||||||
|
desc: Some(desc),
|
||||||
|
file: Some(file),
|
||||||
|
url: None,
|
||||||
|
selected: None,
|
||||||
|
extra: None,
|
||||||
|
updated: Some(help::get_now()),
|
||||||
|
file_data: Some(tmpl::ITEM_MERGE.into()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ## Script type (enhance)
|
||||||
|
/// create the enhanced item by using javascript(browserjs)
|
||||||
|
pub fn from_script(name: String, desc: String) -> Result<PrfItem> {
|
||||||
|
let uid = help::get_uid("s");
|
||||||
|
let file = format!("{uid}.js"); // js ext
|
||||||
|
|
||||||
|
Ok(PrfItem {
|
||||||
|
uid: Some(uid),
|
||||||
|
itype: Some("script".into()),
|
||||||
|
name: Some(name),
|
||||||
|
desc: Some(desc),
|
||||||
|
file: Some(file),
|
||||||
|
url: None,
|
||||||
|
selected: None,
|
||||||
|
extra: None,
|
||||||
|
updated: Some(help::get_now()),
|
||||||
|
file_data: Some(tmpl::ITEM_SCRIPT.into()),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
|
@ -85,9 +85,9 @@ fn main() -> std::io::Result<()> {
|
|||||||
cmds::get_verge_config,
|
cmds::get_verge_config,
|
||||||
cmds::patch_verge_config,
|
cmds::patch_verge_config,
|
||||||
// profile
|
// profile
|
||||||
cmds::new_profile,
|
|
||||||
cmds::view_profile,
|
cmds::view_profile,
|
||||||
cmds::patch_profile,
|
cmds::patch_profile,
|
||||||
|
cmds::create_profile,
|
||||||
cmds::import_profile,
|
cmds::import_profile,
|
||||||
cmds::update_profile,
|
cmds::update_profile,
|
||||||
cmds::delete_profile,
|
cmds::delete_profile,
|
||||||
|
@ -32,11 +32,38 @@ system_proxy_bypass: localhost;127.*;10.*;192.168.*;<local>
|
|||||||
";
|
";
|
||||||
|
|
||||||
/// template for new a profile item
|
/// template for new a profile item
|
||||||
pub const ITEM_CONFIG: &str = "# Profile Template for clash verge\n\n
|
pub const ITEM_LOCAL: &str = "# Profile Template for clash verge
|
||||||
# proxies defination (optional, the same as clash)
|
|
||||||
proxies:\n
|
proxies:
|
||||||
# proxy-groups (optional, the same as clash)
|
|
||||||
proxy-groups:\n
|
proxy-groups:
|
||||||
# rules (optional, the same as clash)
|
|
||||||
rules:\n\n
|
rules:
|
||||||
|
";
|
||||||
|
|
||||||
|
/// enhanced profile
|
||||||
|
pub const ITEM_MERGE: &str = "# Merge Template for clash verge
|
||||||
|
# The `Merge` format used to enhance profile
|
||||||
|
|
||||||
|
prepend-rules:
|
||||||
|
|
||||||
|
prepend-proxies:
|
||||||
|
|
||||||
|
prepend-proxy-groups:
|
||||||
|
|
||||||
|
append-rules:
|
||||||
|
|
||||||
|
append-proxies:
|
||||||
|
|
||||||
|
append-proxy-groups:
|
||||||
|
";
|
||||||
|
|
||||||
|
/// enhanced profile
|
||||||
|
pub const ITEM_SCRIPT: &str = "// Should define the `main` function
|
||||||
|
// The argument to this function is the clash config
|
||||||
|
// or the result of the previous handler
|
||||||
|
// so you should return the config after processing
|
||||||
|
function main(params) {
|
||||||
|
return params;
|
||||||
|
}
|
||||||
";
|
";
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useLockFn } from "ahooks";
|
|
||||||
import { mutate } from "swr";
|
import { mutate } from "swr";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useLockFn, useSetState } from "ahooks";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Dialog,
|
Dialog,
|
||||||
@ -22,66 +22,80 @@ interface Props {
|
|||||||
// edit the profile item
|
// edit the profile item
|
||||||
const ProfileEdit = (props: Props) => {
|
const ProfileEdit = (props: Props) => {
|
||||||
const { open, itemData, onClose } = props;
|
const { open, itemData, onClose } = props;
|
||||||
|
const [form, setForm] = useSetState({ ...itemData });
|
||||||
// todo: more type
|
|
||||||
const [name, setName] = useState(itemData.name);
|
|
||||||
const [desc, setDesc] = useState(itemData.desc);
|
|
||||||
const [url, setUrl] = useState(itemData.url);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (itemData) {
|
if (itemData) {
|
||||||
setName(itemData.name);
|
setForm({ ...itemData });
|
||||||
setDesc(itemData.desc);
|
|
||||||
setUrl(itemData.url);
|
|
||||||
}
|
}
|
||||||
}, [itemData]);
|
}, [itemData]);
|
||||||
|
|
||||||
const onUpdate = useLockFn(async () => {
|
const onUpdate = useLockFn(async () => {
|
||||||
try {
|
try {
|
||||||
const { uid } = itemData;
|
const { uid } = itemData;
|
||||||
|
const { name, desc, url } = form;
|
||||||
await patchProfile(uid, { uid, name, desc, url });
|
await patchProfile(uid, { uid, name, desc, url });
|
||||||
mutate("getProfiles");
|
mutate("getProfiles");
|
||||||
onClose();
|
onClose();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Notice.error(err?.message || err?.toString());
|
Notice.error(err?.message || err.toString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const textFieldProps = {
|
||||||
|
fullWidth: true,
|
||||||
|
size: "small",
|
||||||
|
margin: "normal",
|
||||||
|
variant: "outlined",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
const type =
|
||||||
|
form.type ?? form.url
|
||||||
|
? "remote"
|
||||||
|
: form.file?.endsWith("js")
|
||||||
|
? "script"
|
||||||
|
: "local";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onClose={onClose}>
|
<Dialog open={open} onClose={onClose}>
|
||||||
<DialogTitle>Edit Profile</DialogTitle>
|
<DialogTitle sx={{ pb: 0.5 }}>Edit Profile</DialogTitle>
|
||||||
<DialogContent sx={{ width: 360, pb: 0.5 }}>
|
|
||||||
|
<DialogContent sx={{ width: 336, pb: 1 }}>
|
||||||
<TextField
|
<TextField
|
||||||
|
{...textFieldProps}
|
||||||
|
disabled
|
||||||
|
label="Type"
|
||||||
|
value={type}
|
||||||
|
sx={{ input: { textTransform: "capitalize" } }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
{...textFieldProps}
|
||||||
autoFocus
|
autoFocus
|
||||||
fullWidth
|
|
||||||
label="Name"
|
label="Name"
|
||||||
margin="dense"
|
value={form.name}
|
||||||
variant="outlined"
|
onChange={(e) => setForm({ name: e.target.value })}
|
||||||
value={name}
|
|
||||||
onChange={(e) => setName(e.target.value)}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
{...textFieldProps}
|
||||||
label="Descriptions"
|
label="Descriptions"
|
||||||
margin="normal"
|
value={form.desc}
|
||||||
variant="outlined"
|
onChange={(e) => setForm({ desc: e.target.value })}
|
||||||
value={desc}
|
|
||||||
onChange={(e) => setDesc(e.target.value)}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
{type === "remote" && (
|
||||||
fullWidth
|
<TextField
|
||||||
label="Remote URL"
|
{...textFieldProps}
|
||||||
margin="normal"
|
label="Subscription Url"
|
||||||
variant="outlined"
|
value={form.url}
|
||||||
value={url}
|
onChange={(e) => setForm({ url: e.target.value })}
|
||||||
onChange={(e) => setUrl(e.target.value)}
|
/>
|
||||||
/>
|
)}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
||||||
<DialogActions sx={{ px: 2, pb: 2 }}>
|
<DialogActions sx={{ px: 2, pb: 2 }}>
|
||||||
<Button onClick={onClose}>Cancel</Button>
|
<Button onClick={onClose}>Cancel</Button>
|
||||||
|
|
||||||
<Button onClick={onUpdate} variant="contained">
|
<Button onClick={onUpdate} variant="contained">
|
||||||
Update
|
Update
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
import { useLockFn } from "ahooks";
|
||||||
|
import { useSWRConfig } from "swr";
|
||||||
|
import { useEffect, useState, MouseEvent } from "react";
|
||||||
import {
|
import {
|
||||||
alpha,
|
alpha,
|
||||||
Box,
|
Box,
|
||||||
@ -11,8 +13,6 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
Menu,
|
Menu,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useLockFn } from "ahooks";
|
|
||||||
import { useSWRConfig } from "swr";
|
|
||||||
import { RefreshRounded } from "@mui/icons-material";
|
import { RefreshRounded } from "@mui/icons-material";
|
||||||
import { CmdType } from "../../services/types";
|
import { CmdType } from "../../services/types";
|
||||||
import { updateProfile, deleteProfile, viewProfile } from "../../services/cmds";
|
import { updateProfile, deleteProfile, viewProfile } from "../../services/cmds";
|
||||||
@ -48,7 +48,7 @@ interface Props {
|
|||||||
onSelect: (force: boolean) => void;
|
onSelect: (force: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProfileItem: React.FC<Props> = (props) => {
|
const ProfileItem = (props: Props) => {
|
||||||
const { selected, itemData, onSelect } = props;
|
const { selected, itemData, onSelect } = props;
|
||||||
|
|
||||||
const { mutate } = useSWRConfig();
|
const { mutate } = useSWRConfig();
|
||||||
@ -118,9 +118,7 @@ const ProfileItem: React.FC<Props> = (props) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleContextMenu = (
|
const handleContextMenu = (event: MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||||
event: React.MouseEvent<HTMLDivElement, MouseEvent>
|
|
||||||
) => {
|
|
||||||
const { clientX, clientY } = event;
|
const { clientX, clientY } = event;
|
||||||
setPosition({ top: clientY, left: clientX });
|
setPosition({ top: clientY, left: clientX });
|
||||||
setAnchorEl(event.currentTarget);
|
setAnchorEl(event.currentTarget);
|
||||||
@ -180,7 +178,7 @@ const ProfileItem: React.FC<Props> = (props) => {
|
|||||||
return { bgcolor, color, "& h2": { color: h2color } };
|
return { bgcolor, color, "& h2": { color: h2color } };
|
||||||
}}
|
}}
|
||||||
onClick={() => onSelect(false)}
|
onClick={() => onSelect(false)}
|
||||||
onContextMenu={handleContextMenu}
|
onContextMenu={handleContextMenu as any}
|
||||||
>
|
>
|
||||||
<Box display="flex" justifyContent="space-between">
|
<Box display="flex" justifyContent="space-between">
|
||||||
<Typography
|
<Typography
|
||||||
|
@ -1,63 +1,105 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useSWRConfig } from "swr";
|
||||||
|
import { useLockFn, useSetState } from "ahooks";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
|
FormControl,
|
||||||
|
InputLabel,
|
||||||
|
MenuItem,
|
||||||
|
Select,
|
||||||
TextField,
|
TextField,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
import { createProfile } from "../../services/cmds";
|
||||||
import Notice from "../base/base-notice";
|
import Notice from "../base/base-notice";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSubmit: (name: string, desc: string) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// create a new profile
|
||||||
|
// remote / local file / merge / script
|
||||||
const ProfileNew = (props: Props) => {
|
const ProfileNew = (props: Props) => {
|
||||||
const { open, onClose, onSubmit } = props;
|
const { open, onClose } = props;
|
||||||
const [name, setName] = useState("");
|
|
||||||
const [desc, setDesc] = useState("");
|
|
||||||
|
|
||||||
const onCreate = () => {
|
const { mutate } = useSWRConfig();
|
||||||
if (!name.trim()) {
|
const [form, setForm] = useSetState({
|
||||||
Notice.error("`Name` should not be null");
|
name: "",
|
||||||
|
desc: "",
|
||||||
|
type: "remote",
|
||||||
|
url: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const onCreate = useLockFn(async () => {
|
||||||
|
if (!form.type) {
|
||||||
|
Notice.error("`Type` should not be null");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
onSubmit(name, desc);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
try {
|
||||||
if (!open) {
|
await createProfile({ ...form });
|
||||||
setName("");
|
setForm({ name: "", desc: "", type: "remote", url: "" });
|
||||||
setDesc("");
|
mutate("getProfiles");
|
||||||
|
onClose();
|
||||||
|
} catch (err: any) {
|
||||||
|
Notice.error(err.message || err.toString());
|
||||||
}
|
}
|
||||||
}, [open]);
|
});
|
||||||
|
|
||||||
|
const textFieldProps = {
|
||||||
|
fullWidth: true,
|
||||||
|
size: "small",
|
||||||
|
margin: "normal",
|
||||||
|
variant: "outlined",
|
||||||
|
} as const;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onClose={onClose}>
|
<Dialog open={open} onClose={onClose}>
|
||||||
<DialogTitle>Create Profile</DialogTitle>
|
<DialogTitle sx={{ pb: 0.5 }}>Create Profile</DialogTitle>
|
||||||
<DialogContent sx={{ width: 320, pb: 0.5 }}>
|
|
||||||
|
<DialogContent sx={{ width: 336, pb: 1 }}>
|
||||||
<TextField
|
<TextField
|
||||||
|
{...textFieldProps}
|
||||||
autoFocus
|
autoFocus
|
||||||
fullWidth
|
|
||||||
label="Name"
|
label="Name"
|
||||||
margin="dense"
|
value={form.name}
|
||||||
variant="outlined"
|
onChange={(e) => setForm({ name: e.target.value })}
|
||||||
value={name}
|
|
||||||
onChange={(e) => setName(e.target.value)}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<FormControl size="small" fullWidth sx={{ mt: 2, mb: 1 }}>
|
||||||
|
<InputLabel>Type</InputLabel>
|
||||||
|
<Select
|
||||||
|
label="Type"
|
||||||
|
value={form.type}
|
||||||
|
onChange={(e) => setForm({ type: e.target.value })}
|
||||||
|
>
|
||||||
|
<MenuItem value="remote">Remote</MenuItem>
|
||||||
|
<MenuItem value="local">Local</MenuItem>
|
||||||
|
<MenuItem value="script">Script</MenuItem>
|
||||||
|
<MenuItem value="merge">Merge</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
{...textFieldProps}
|
||||||
label="Descriptions"
|
label="Descriptions"
|
||||||
margin="normal"
|
value={form.desc}
|
||||||
variant="outlined"
|
onChange={(e) => setForm({ desc: e.target.value })}
|
||||||
value={desc}
|
|
||||||
onChange={(e) => setDesc(e.target.value)}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{form.type === "remote" && (
|
||||||
|
<TextField
|
||||||
|
{...textFieldProps}
|
||||||
|
label="Subscription Url"
|
||||||
|
value={form.url}
|
||||||
|
onChange={(e) => setForm({ url: e.target.value })}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
||||||
<DialogActions sx={{ px: 2, pb: 2 }}>
|
<DialogActions sx={{ px: 2, pb: 2 }}>
|
||||||
<Button onClick={onClose}>Cancel</Button>
|
<Button onClick={onClose}>Cancel</Button>
|
||||||
<Button onClick={onCreate} variant="contained">
|
<Button onClick={onCreate} variant="contained">
|
||||||
|
@ -1,19 +1,18 @@
|
|||||||
import useSWR, { useSWRConfig } from "swr";
|
import useSWR, { useSWRConfig } from "swr";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useLockFn } from "ahooks";
|
import { useLockFn } from "ahooks";
|
||||||
import { Box, Button, Grid, TextField } from "@mui/material";
|
import { Box, Button, Grid, TextField, Typography } from "@mui/material";
|
||||||
import {
|
import {
|
||||||
getProfiles,
|
getProfiles,
|
||||||
selectProfile,
|
selectProfile,
|
||||||
patchProfile,
|
patchProfile,
|
||||||
importProfile,
|
importProfile,
|
||||||
newProfile,
|
|
||||||
} from "../services/cmds";
|
} from "../services/cmds";
|
||||||
import { getProxies, updateProxy } from "../services/api";
|
import { getProxies, updateProxy } from "../services/api";
|
||||||
import Notice from "../components/base/base-notice";
|
import Notice from "../components/base/base-notice";
|
||||||
import BasePage from "../components/base/base-page";
|
import BasePage from "../components/base/base-page";
|
||||||
import ProfileItem from "../components/profile/profile-item";
|
|
||||||
import ProfileNew from "../components/profile/profile-new";
|
import ProfileNew from "../components/profile/profile-new";
|
||||||
|
import ProfileItem from "../components/profile/profile-item";
|
||||||
|
|
||||||
const ProfilePage = () => {
|
const ProfilePage = () => {
|
||||||
const [url, setUrl] = useState("");
|
const [url, setUrl] = useState("");
|
||||||
@ -21,6 +20,7 @@ const ProfilePage = () => {
|
|||||||
|
|
||||||
const { mutate } = useSWRConfig();
|
const { mutate } = useSWRConfig();
|
||||||
const { data: profiles = {} } = useSWR("getProfiles", getProfiles);
|
const { data: profiles = {} } = useSWR("getProfiles", getProfiles);
|
||||||
|
const [dialogOpen, setDialogOpen] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (profiles.current == null) return;
|
if (profiles.current == null) return;
|
||||||
@ -96,18 +96,7 @@ const ProfilePage = () => {
|
|||||||
await selectProfile(current);
|
await selectProfile(current);
|
||||||
mutate("getProfiles", { ...profiles, current: current }, true);
|
mutate("getProfiles", { ...profiles, current: current }, true);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
err && Notice.error(err.toString());
|
err && Notice.error(err.message || err.toString());
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const [dialogOpen, setDialogOpen] = useState(false);
|
|
||||||
const onNew = useLockFn(async (name: string, desc: string) => {
|
|
||||||
try {
|
|
||||||
await newProfile(name, desc);
|
|
||||||
setDialogOpen(false);
|
|
||||||
mutate("getProfiles");
|
|
||||||
} catch (err: any) {
|
|
||||||
err && Notice.error(err.toString());
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -149,11 +138,13 @@ const ProfilePage = () => {
|
|||||||
))}
|
))}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<ProfileNew
|
<ProfileNew open={dialogOpen} onClose={() => setDialogOpen(false)} />
|
||||||
open={dialogOpen}
|
|
||||||
onClose={() => setDialogOpen(false)}
|
<header data-windrag style={{ marginTop: 20, userSelect: "none" }}>
|
||||||
onSubmit={onNew}
|
<Typography variant="h5" component="h2" data-windrag>
|
||||||
/>
|
Enhanced
|
||||||
|
</Typography>
|
||||||
|
</header>
|
||||||
</BasePage>
|
</BasePage>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -9,8 +9,8 @@ export async function syncProfiles() {
|
|||||||
return invoke<void>("sync_profiles");
|
return invoke<void>("sync_profiles");
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function newProfile(name: string, desc: string) {
|
export async function createProfile(item: Partial<CmdType.ProfileItem>) {
|
||||||
return invoke<void>("new_profile", { name, desc });
|
return invoke<void>("create_profile", { item });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function viewProfile(index: string) {
|
export async function viewProfile(index: string) {
|
||||||
|
@ -78,6 +78,8 @@ export namespace ApiType {
|
|||||||
* Some interface for command
|
* Some interface for command
|
||||||
*/
|
*/
|
||||||
export namespace CmdType {
|
export namespace CmdType {
|
||||||
|
export type ProfileType = "local" | "remote" | "merge" | "script";
|
||||||
|
|
||||||
export interface ClashInfo {
|
export interface ClashInfo {
|
||||||
status: string;
|
status: string;
|
||||||
port?: string;
|
port?: string;
|
||||||
@ -87,7 +89,7 @@ export namespace CmdType {
|
|||||||
|
|
||||||
export interface ProfileItem {
|
export interface ProfileItem {
|
||||||
uid: string;
|
uid: string;
|
||||||
type?: string;
|
type?: ProfileType | string;
|
||||||
name?: string;
|
name?: string;
|
||||||
desc?: string;
|
desc?: string;
|
||||||
file?: string;
|
file?: string;
|
||||||
|
Loading…
Reference in New Issue
Block a user