feat: profile item adjust

This commit is contained in:
GyDi
2022-03-05 19:04:20 +08:00
Unverified
parent b758ead371
commit 83bf67b8ca
10 changed files with 262 additions and 108 deletions

View File

@@ -1,6 +1,6 @@
import { useEffect, useState } from "react";
import { useLockFn } from "ahooks";
import { mutate } from "swr";
import { useEffect } from "react";
import { useLockFn, useSetState } from "ahooks";
import {
Button,
Dialog,
@@ -22,66 +22,80 @@ interface Props {
// edit the profile item
const ProfileEdit = (props: Props) => {
const { open, itemData, onClose } = props;
// todo: more type
const [name, setName] = useState(itemData.name);
const [desc, setDesc] = useState(itemData.desc);
const [url, setUrl] = useState(itemData.url);
const [form, setForm] = useSetState({ ...itemData });
useEffect(() => {
if (itemData) {
setName(itemData.name);
setDesc(itemData.desc);
setUrl(itemData.url);
setForm({ ...itemData });
}
}, [itemData]);
const onUpdate = useLockFn(async () => {
try {
const { uid } = itemData;
const { name, desc, url } = form;
await patchProfile(uid, { uid, name, desc, url });
mutate("getProfiles");
onClose();
} 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 (
<Dialog open={open} onClose={onClose}>
<DialogTitle>Edit Profile</DialogTitle>
<DialogContent sx={{ width: 360, pb: 0.5 }}>
<DialogTitle sx={{ pb: 0.5 }}>Edit Profile</DialogTitle>
<DialogContent sx={{ width: 336, pb: 1 }}>
<TextField
{...textFieldProps}
disabled
label="Type"
value={type}
sx={{ input: { textTransform: "capitalize" } }}
/>
<TextField
{...textFieldProps}
autoFocus
fullWidth
label="Name"
margin="dense"
variant="outlined"
value={name}
onChange={(e) => setName(e.target.value)}
value={form.name}
onChange={(e) => setForm({ name: e.target.value })}
/>
<TextField
fullWidth
{...textFieldProps}
label="Descriptions"
margin="normal"
variant="outlined"
value={desc}
onChange={(e) => setDesc(e.target.value)}
value={form.desc}
onChange={(e) => setForm({ desc: e.target.value })}
/>
<TextField
fullWidth
label="Remote URL"
margin="normal"
variant="outlined"
value={url}
onChange={(e) => setUrl(e.target.value)}
/>
{type === "remote" && (
<TextField
{...textFieldProps}
label="Subscription Url"
value={form.url}
onChange={(e) => setForm({ url: e.target.value })}
/>
)}
</DialogContent>
<DialogActions sx={{ px: 2, pb: 2 }}>
<Button onClick={onClose}>Cancel</Button>
<Button onClick={onUpdate} variant="contained">
Update
</Button>

View File

@@ -1,5 +1,7 @@
import React, { useEffect, useState } from "react";
import dayjs from "dayjs";
import { useLockFn } from "ahooks";
import { useSWRConfig } from "swr";
import { useEffect, useState, MouseEvent } from "react";
import {
alpha,
Box,
@@ -11,8 +13,6 @@ import {
MenuItem,
Menu,
} from "@mui/material";
import { useLockFn } from "ahooks";
import { useSWRConfig } from "swr";
import { RefreshRounded } from "@mui/icons-material";
import { CmdType } from "../../services/types";
import { updateProfile, deleteProfile, viewProfile } from "../../services/cmds";
@@ -48,7 +48,7 @@ interface Props {
onSelect: (force: boolean) => void;
}
const ProfileItem: React.FC<Props> = (props) => {
const ProfileItem = (props: Props) => {
const { selected, itemData, onSelect } = props;
const { mutate } = useSWRConfig();
@@ -118,9 +118,7 @@ const ProfileItem: React.FC<Props> = (props) => {
}
});
const handleContextMenu = (
event: React.MouseEvent<HTMLDivElement, MouseEvent>
) => {
const handleContextMenu = (event: MouseEvent<HTMLDivElement, MouseEvent>) => {
const { clientX, clientY } = event;
setPosition({ top: clientY, left: clientX });
setAnchorEl(event.currentTarget);
@@ -180,7 +178,7 @@ const ProfileItem: React.FC<Props> = (props) => {
return { bgcolor, color, "& h2": { color: h2color } };
}}
onClick={() => onSelect(false)}
onContextMenu={handleContextMenu}
onContextMenu={handleContextMenu as any}
>
<Box display="flex" justifyContent="space-between">
<Typography

View File

@@ -1,63 +1,105 @@
import { useEffect, useState } from "react";
import { useSWRConfig } from "swr";
import { useLockFn, useSetState } from "ahooks";
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
FormControl,
InputLabel,
MenuItem,
Select,
TextField,
} from "@mui/material";
import { createProfile } from "../../services/cmds";
import Notice from "../base/base-notice";
interface Props {
open: boolean;
onClose: () => void;
onSubmit: (name: string, desc: string) => void;
}
// create a new profile
// remote / local file / merge / script
const ProfileNew = (props: Props) => {
const { open, onClose, onSubmit } = props;
const [name, setName] = useState("");
const [desc, setDesc] = useState("");
const { open, onClose } = props;
const onCreate = () => {
if (!name.trim()) {
Notice.error("`Name` should not be null");
const { mutate } = useSWRConfig();
const [form, setForm] = useSetState({
name: "",
desc: "",
type: "remote",
url: "",
});
const onCreate = useLockFn(async () => {
if (!form.type) {
Notice.error("`Type` should not be null");
return;
}
onSubmit(name, desc);
};
useEffect(() => {
if (!open) {
setName("");
setDesc("");
try {
await createProfile({ ...form });
setForm({ name: "", desc: "", type: "remote", url: "" });
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 (
<Dialog open={open} onClose={onClose}>
<DialogTitle>Create Profile</DialogTitle>
<DialogContent sx={{ width: 320, pb: 0.5 }}>
<DialogTitle sx={{ pb: 0.5 }}>Create Profile</DialogTitle>
<DialogContent sx={{ width: 336, pb: 1 }}>
<TextField
{...textFieldProps}
autoFocus
fullWidth
label="Name"
margin="dense"
variant="outlined"
value={name}
onChange={(e) => setName(e.target.value)}
value={form.name}
onChange={(e) => setForm({ name: 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
fullWidth
{...textFieldProps}
label="Descriptions"
margin="normal"
variant="outlined"
value={desc}
onChange={(e) => setDesc(e.target.value)}
value={form.desc}
onChange={(e) => setForm({ desc: e.target.value })}
/>
{form.type === "remote" && (
<TextField
{...textFieldProps}
label="Subscription Url"
value={form.url}
onChange={(e) => setForm({ url: e.target.value })}
/>
)}
</DialogContent>
<DialogActions sx={{ px: 2, pb: 2 }}>
<Button onClick={onClose}>Cancel</Button>
<Button onClick={onCreate} variant="contained">

View File

@@ -1,19 +1,18 @@
import useSWR, { useSWRConfig } from "swr";
import { useEffect, useState } from "react";
import { useLockFn } from "ahooks";
import { Box, Button, Grid, TextField } from "@mui/material";
import { Box, Button, Grid, TextField, Typography } from "@mui/material";
import {
getProfiles,
selectProfile,
patchProfile,
importProfile,
newProfile,
} from "../services/cmds";
import { getProxies, updateProxy } from "../services/api";
import Notice from "../components/base/base-notice";
import BasePage from "../components/base/base-page";
import ProfileItem from "../components/profile/profile-item";
import ProfileNew from "../components/profile/profile-new";
import ProfileItem from "../components/profile/profile-item";
const ProfilePage = () => {
const [url, setUrl] = useState("");
@@ -21,6 +20,7 @@ const ProfilePage = () => {
const { mutate } = useSWRConfig();
const { data: profiles = {} } = useSWR("getProfiles", getProfiles);
const [dialogOpen, setDialogOpen] = useState(false);
useEffect(() => {
if (profiles.current == null) return;
@@ -96,18 +96,7 @@ const ProfilePage = () => {
await selectProfile(current);
mutate("getProfiles", { ...profiles, current: current }, true);
} catch (err: any) {
err && Notice.error(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());
err && Notice.error(err.message || err.toString());
}
});
@@ -149,11 +138,13 @@ const ProfilePage = () => {
))}
</Grid>
<ProfileNew
open={dialogOpen}
onClose={() => setDialogOpen(false)}
onSubmit={onNew}
/>
<ProfileNew open={dialogOpen} onClose={() => setDialogOpen(false)} />
<header data-windrag style={{ marginTop: 20, userSelect: "none" }}>
<Typography variant="h5" component="h2" data-windrag>
Enhanced
</Typography>
</header>
</BasePage>
);
};

View File

@@ -9,8 +9,8 @@ export async function syncProfiles() {
return invoke<void>("sync_profiles");
}
export async function newProfile(name: string, desc: string) {
return invoke<void>("new_profile", { name, desc });
export async function createProfile(item: Partial<CmdType.ProfileItem>) {
return invoke<void>("create_profile", { item });
}
export async function viewProfile(index: string) {

View File

@@ -78,6 +78,8 @@ export namespace ApiType {
* Some interface for command
*/
export namespace CmdType {
export type ProfileType = "local" | "remote" | "merge" | "script";
export interface ClashInfo {
status: string;
port?: string;
@@ -87,7 +89,7 @@ export namespace CmdType {
export interface ProfileItem {
uid: string;
type?: string;
type?: ProfileType | string;
name?: string;
desc?: string;
file?: string;