diff --git a/src/components/profile/file-editor.tsx b/src/components/profile/editor-viewer.tsx similarity index 98% rename from src/components/profile/file-editor.tsx rename to src/components/profile/editor-viewer.tsx index 5f9bab779a75dc62a0e57d5d3dca4d8360befae9..ea15f5faee06e2961171a387a65e49de620b1803 100644 --- a/src/components/profile/file-editor.tsx +++ b/src/components/profile/editor-viewer.tsx @@ -26,7 +26,7 @@ interface Props { onChange?: () => void; } -export const FileEditor = (props: Props) => { +export const EditorViewer = (props: Props) => { const { uid, open, mode, onClose, onChange } = props; const { t } = useTranslation(); diff --git a/src/components/profile/enhanced.tsx b/src/components/profile/enhanced.tsx index 2cc68c90b95d4100ed5a7e5a34e0ce0e7a0361f2..6c20eb8375aa8b1df1af9c3c776595b822fa55f9 100644 --- a/src/components/profile/enhanced.tsx +++ b/src/components/profile/enhanced.tsx @@ -1,44 +1,29 @@ import useSWR from "swr"; import { useLockFn } from "ahooks"; -import { useTranslation } from "react-i18next"; -import { Box, Grid, IconButton, Stack } from "@mui/material"; -import { RestartAltRounded } from "@mui/icons-material"; +import { Grid } from "@mui/material"; import { getProfiles, deleteProfile, - enhanceProfiles, patchProfilesConfig, getRuntimeLogs, } from "@/services/cmds"; import { Notice } from "@/components/base"; -import ProfileMore from "./profile-more"; +import { ProfileMore } from "./profile-more"; interface Props { items: IProfileItem[]; chain: string[]; } -const EnhancedMode = (props: Props) => { +export const EnhancedMode = (props: Props) => { const { items, chain } = props; - const { t } = useTranslation(); const { mutate: mutateProfiles } = useSWR("getProfiles", getProfiles); const { data: chainLogs = {}, mutate: mutateLogs } = useSWR( "getRuntimeLogs", getRuntimeLogs ); - // handler - const onEnhance = useLockFn(async () => { - try { - await enhanceProfiles(); - mutateLogs(); - // Notice.success("Refresh clash config", 1000); - } catch (err: any) { - Notice.error(err.message || err.toString()); - } - }); - const onEnhanceEnable = useLockFn(async (uid: string) => { if (chain.includes(uid)) return; @@ -87,43 +72,22 @@ const EnhancedMode = (props: Props) => { }); return ( - <Box sx={{ mt: 2 }}> - <Stack - spacing={1} - direction="row" - alignItems="center" - justifyContent="flex-end" - sx={{ mb: 0.5 }} - > - <IconButton - size="small" - color="inherit" - title={t("Refresh profiles")} - onClick={onEnhance} - > - <RestartAltRounded /> - </IconButton> - </Stack> - - <Grid container spacing={2}> - {items.map((item) => ( - <Grid item xs={12} sm={6} key={item.file}> - <ProfileMore - selected={!!chain.includes(item.uid)} - itemData={item} - enableNum={chain.length} - logInfo={chainLogs[item.uid]} - onEnable={() => onEnhanceEnable(item.uid)} - onDisable={() => onEnhanceDisable(item.uid)} - onDelete={() => onEnhanceDelete(item.uid)} - onMoveTop={() => onMoveTop(item.uid)} - onMoveEnd={() => onMoveEnd(item.uid)} - /> - </Grid> - ))} - </Grid> - </Box> + <Grid container spacing={{ xs: 2, lg: 3 }}> + {items.map((item) => ( + <Grid item xs={12} sm={6} md={4} lg={3} key={item.file}> + <ProfileMore + selected={!!chain.includes(item.uid)} + itemData={item} + enableNum={chain.length} + logInfo={chainLogs[item.uid]} + onEnable={() => onEnhanceEnable(item.uid)} + onDisable={() => onEnhanceDisable(item.uid)} + onDelete={() => onEnhanceDelete(item.uid)} + onMoveTop={() => onMoveTop(item.uid)} + onMoveEnd={() => onMoveEnd(item.uid)} + /> + </Grid> + ))} + </Grid> ); }; - -export default EnhancedMode; diff --git a/src/components/profile/file-input.tsx b/src/components/profile/file-input.tsx index d86070969970958021c259ff123f04ef2628c3c0..5357224a464b5ee18177ce0e4c5ed8fa9cc55ab9 100644 --- a/src/components/profile/file-input.tsx +++ b/src/components/profile/file-input.tsx @@ -7,7 +7,7 @@ interface Props { onChange: (value: string) => void; } -const FileInput = (props: Props) => { +export const FileInput = (props: Props) => { const { onChange } = props; const { t } = useTranslation(); @@ -59,5 +59,3 @@ const FileInput = (props: Props) => { </Box> ); }; - -export default FileInput; diff --git a/src/components/profile/info-editor.tsx b/src/components/profile/info-viewer.tsx similarity index 98% rename from src/components/profile/info-editor.tsx rename to src/components/profile/info-viewer.tsx index f5c0fb0a376e4efb0a0cffdce8bdc49680642e04..54215a63226c4fda9e5ce3ac28f877c8e57a9e5c 100644 --- a/src/components/profile/info-editor.tsx +++ b/src/components/profile/info-viewer.tsx @@ -27,7 +27,7 @@ interface Props { // edit the profile item // remote / local file / merge / script -const InfoEditor = (props: Props) => { +export const InfoViewer = (props: Props) => { const { open, itemData, onClose } = props; const { t } = useTranslation(); @@ -209,5 +209,3 @@ const InfoEditor = (props: Props) => { </Dialog> ); }; - -export default InfoEditor; diff --git a/src/components/profile/log-viewer.tsx b/src/components/profile/log-viewer.tsx index c3ba16265103857cb7ab276c5aa1cb16de0e1856..47924683712f87265e2edd096418ddf8587630b4 100644 --- a/src/components/profile/log-viewer.tsx +++ b/src/components/profile/log-viewer.tsx @@ -18,7 +18,7 @@ interface Props { onClose: () => void; } -const LogViewer = (props: Props) => { +export const LogViewer = (props: Props) => { const { open, logInfo, onClose } = props; const { t } = useTranslation(); @@ -67,5 +67,3 @@ const LogViewer = (props: Props) => { </Dialog> ); }; - -export default LogViewer; diff --git a/src/components/profile/profile-box.tsx b/src/components/profile/profile-box.tsx index 7b8d5e8ef64ea061ee3e2e60c65c1141168de26e..a0fc4a160cc097d3b878dbf786f0458a4da677e9 100644 --- a/src/components/profile/profile-box.tsx +++ b/src/components/profile/profile-box.tsx @@ -1,43 +1,43 @@ import { alpha, Box, styled } from "@mui/material"; -const ProfileBox = styled(Box)(({ theme, "aria-selected": selected }) => { - const { mode, primary, text, grey, background } = theme.palette; - const key = `${mode}-${!!selected}`; +export const ProfileBox = styled(Box)( + ({ theme, "aria-selected": selected }) => { + const { mode, primary, text, grey, background } = theme.palette; + const key = `${mode}-${!!selected}`; - const backgroundColor = { - "light-true": alpha(primary.main, 0.2), - "light-false": alpha(background.paper, 0.75), - "dark-true": alpha(primary.main, 0.45), - "dark-false": alpha(grey[700], 0.45), - }[key]!; + const backgroundColor = { + "light-true": alpha(primary.main, 0.2), + "light-false": alpha(background.paper, 0.75), + "dark-true": alpha(primary.main, 0.45), + "dark-false": alpha(grey[700], 0.45), + }[key]!; - const color = { - "light-true": text.secondary, - "light-false": text.secondary, - "dark-true": alpha(text.secondary, 0.85), - "dark-false": alpha(text.secondary, 0.65), - }[key]!; + const color = { + "light-true": text.secondary, + "light-false": text.secondary, + "dark-true": alpha(text.secondary, 0.85), + "dark-false": alpha(text.secondary, 0.65), + }[key]!; - const h2color = { - "light-true": primary.main, - "light-false": text.primary, - "dark-true": primary.light, - "dark-false": text.primary, - }[key]!; + const h2color = { + "light-true": primary.main, + "light-false": text.primary, + "dark-true": primary.light, + "dark-false": text.primary, + }[key]!; - return { - width: "100%", - display: "block", - cursor: "pointer", - textAlign: "left", - borderRadius: theme.shape.borderRadius, - boxShadow: theme.shadows[2], - padding: "8px 16px", - boxSizing: "border-box", - backgroundColor, - color, - "& h2": { color: h2color }, - }; -}); - -export default ProfileBox; + return { + width: "100%", + display: "block", + cursor: "pointer", + textAlign: "left", + borderRadius: theme.shape.borderRadius, + boxShadow: theme.shadows[2], + padding: "8px 16px", + boxSizing: "border-box", + backgroundColor, + color, + "& h2": { color: h2color }, + }; + } +); diff --git a/src/components/profile/profile-item.tsx b/src/components/profile/profile-item.tsx index a79e9825538d680f4f20bff9209a646c4ad76dd3..28400171d32819ae2d0a9f808c67cc0fc90c68f9 100644 --- a/src/components/profile/profile-item.tsx +++ b/src/components/profile/profile-item.tsx @@ -17,10 +17,10 @@ import { RefreshRounded } from "@mui/icons-material"; import { atomLoadingCache } from "@/services/states"; import { updateProfile, deleteProfile, viewProfile } from "@/services/cmds"; import { Notice } from "@/components/base"; +import { InfoViewer } from "./info-viewer"; +import { EditorViewer } from "./editor-viewer"; +import { ProfileBox } from "./profile-box"; import parseTraffic from "@/utils/parse-traffic"; -import ProfileBox from "./profile-box"; -import InfoEditor from "./info-editor"; -import { FileEditor } from "./file-editor"; const round = keyframes` from { transform: rotate(0deg); } @@ -33,7 +33,7 @@ interface Props { onSelect: (force: boolean) => void; } -const ProfileItem = (props: Props) => { +export const ProfileItem = (props: Props) => { const { selected, itemData, onSelect } = props; const { t } = useTranslation(); @@ -298,13 +298,13 @@ const ProfileItem = (props: Props) => { ))} </Menu> - <InfoEditor + <InfoViewer open={editOpen} itemData={itemData} onClose={() => setEditOpen(false)} /> - <FileEditor + <EditorViewer uid={uid} open={fileOpen} mode="yaml" @@ -325,5 +325,3 @@ function parseExpire(expire?: number) { if (!expire) return "-"; return dayjs(expire * 1000).format("YYYY-MM-DD"); } - -export default ProfileItem; diff --git a/src/components/profile/profile-more.tsx b/src/components/profile/profile-more.tsx index e46daaedfefb950513ff10f9c6838d124d644bac..4094cadcd3b3aef02a2ed6af28e1626c0c1aad4a 100644 --- a/src/components/profile/profile-more.tsx +++ b/src/components/profile/profile-more.tsx @@ -14,10 +14,10 @@ import { import { FeaturedPlayListRounded } from "@mui/icons-material"; import { viewProfile } from "@/services/cmds"; import { Notice } from "@/components/base"; -import InfoEditor from "./info-editor"; -import { FileEditor } from "./file-editor"; -import ProfileBox from "./profile-box"; -import LogViewer from "./log-viewer"; +import { InfoViewer } from "./info-viewer"; +import { EditorViewer } from "./editor-viewer"; +import { ProfileBox } from "./profile-box"; +import { LogViewer } from "./log-viewer"; interface Props { selected: boolean; @@ -32,7 +32,7 @@ interface Props { } // profile enhanced item -const ProfileMore = (props: Props) => { +export const ProfileMore = (props: Props) => { const { selected, itemData, @@ -219,13 +219,13 @@ const ProfileMore = (props: Props) => { ))} </Menu> - <InfoEditor + <InfoViewer open={editOpen} itemData={itemData} onClose={() => setEditOpen(false)} /> - <FileEditor + <EditorViewer uid={uid} open={fileOpen} mode={type === "merge" ? "yaml" : "javascript"} @@ -247,5 +247,3 @@ function parseExpire(expire?: number) { if (!expire) return "-"; return dayjs(expire * 1000).format("YYYY-MM-DD"); } - -export default ProfileMore; diff --git a/src/components/profile/profile-new.tsx b/src/components/profile/profile-new.tsx index c75c6ec7d1a7b70d826c4f7fe4fbd898e10796b2..e7f92c9c0a938b0da059a83ad160f398b83e8fd5 100644 --- a/src/components/profile/profile-new.tsx +++ b/src/components/profile/profile-new.tsx @@ -21,7 +21,7 @@ import { import { Settings } from "@mui/icons-material"; import { createProfile } from "@/services/cmds"; import { Notice } from "@/components/base"; -import FileInput from "./file-input"; +import { FileInput } from "./file-input"; interface Props { open: boolean; @@ -30,7 +30,7 @@ interface Props { // create a new profile // remote / local file / merge / script -const ProfileNew = (props: Props) => { +export const ProfileNew = (props: Props) => { const { open, onClose } = props; const { t } = useTranslation(); @@ -210,5 +210,3 @@ const ProfileNew = (props: Props) => { </Dialog> ); }; - -export default ProfileNew; diff --git a/src/pages/profiles.tsx b/src/pages/profiles.tsx index 15cd711b078b765a31f6d8eddd82f54ea3c206cc..23ea7c1cf7ecb0c320876d8c30c73cea5c08015c 100644 --- a/src/pages/profiles.tsx +++ b/src/pages/profiles.tsx @@ -2,20 +2,22 @@ import useSWR, { mutate } from "swr"; import { useLockFn } from "ahooks"; import { useEffect, useMemo, useState } from "react"; import { useSetRecoilState } from "recoil"; -import { Button, Grid, Stack, TextField } from "@mui/material"; +import { Box, Button, Grid, IconButton, Stack, TextField } from "@mui/material"; +import { CachedRounded } from "@mui/icons-material"; import { useTranslation } from "react-i18next"; import { getProfiles, patchProfile, patchProfilesConfig, importProfile, + enhanceProfiles, } from "@/services/cmds"; import { getProxies, updateProxy } from "@/services/api"; import { atomCurrentProfile } from "@/services/states"; import { BasePage, Notice } from "@/components/base"; -import ProfileNew from "@/components/profile/profile-new"; -import ProfileItem from "@/components/profile/profile-item"; -import EnhancedMode from "@/components/profile/enhanced"; +import { ProfileNew } from "@/components/profile/profile-new"; +import { ProfileItem } from "@/components/profile/profile-item"; +import { EnhancedMode } from "@/components/profile/enhanced"; const ProfilePage = () => { const { t } = useTranslation(); @@ -138,8 +140,32 @@ const ProfilePage = () => { } }); + const onEnhance = useLockFn(async () => { + try { + await enhanceProfiles(); + mutate("getRuntimeLogs"); + // Notice.success("Refresh clash config", 1000); + } catch (err: any) { + Notice.error(err.message || err.toString(), 3000); + } + }); + return ( - <BasePage title={t("Profiles")}> + <BasePage + title={t("Profiles")} + header={ + <Box sx={{ mt: 1, display: "flex", alignItems: "center" }}> + <IconButton + size="small" + color="inherit" + title={t("Refresh profiles")} + onClick={onEnhance} + > + <CachedRounded /> + </IconButton> + </Box> + } + > <Stack direction="row" spacing={1} sx={{ mb: 2 }}> <TextField hiddenLabel @@ -170,17 +196,19 @@ const ProfilePage = () => { </Button> </Stack> - <Grid container spacing={2}> - {regularItems.map((item) => ( - <Grid item xs={12} sm={6} key={item.file}> - <ProfileItem - selected={profiles.current === item.uid} - itemData={item} - onSelect={(f) => onSelect(item.uid, f)} - /> - </Grid> - ))} - </Grid> + <Box sx={{ mb: 4.5 }}> + <Grid container spacing={{ xs: 2, lg: 3 }}> + {regularItems.map((item) => ( + <Grid item xs={12} sm={6} md={4} lg={3} key={item.file}> + <ProfileItem + selected={profiles.current === item.uid} + itemData={item} + onSelect={(f) => onSelect(item.uid, f)} + /> + </Grid> + ))} + </Grid> + </Box> {enhanceItems.length > 0 && ( <EnhancedMode items={enhanceItems} chain={profiles.chain || []} />