Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 🎸 update network state #950

Merged
merged 1 commit into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion enjoy/src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -655,8 +655,10 @@
"enrollments": "Enrollments",
"noLikesYet": "No likes yet",
"apiConnectTime": "API Connect Time ({{apiUrl}})",
"storageConnectTime": "Storage Connect Time",
"ipInfo": "IP Information",
"platformInfo": "Platform Information",
"networkState": "Network State",
"connectError": "Connect Error"
"connectError": "Connect Error",
"refresh": "refresh"
}
6 changes: 4 additions & 2 deletions enjoy/src/i18n/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -654,9 +654,11 @@
"enrollNow": "加入练习",
"enrollments": "参加的课程",
"noLikesYet": "还没有点赞",
"apiConnectTime": "API 延时 ({{apiUrl}})",
"apiConnectTime": "接口服务器延时 ({{apiUrl}})",
"storageConnectTime": "资源服务器延时",
"ipInfo": "IP 信息",
"platformInfo": "设备信息",
"networkState": "网络状态",
"connectError": "连接错误"
"connectError": "连接错误",
"refresh": "刷新"
}
246 changes: 138 additions & 108 deletions enjoy/src/renderer/components/preferences/network-state.tsx
Original file line number Diff line number Diff line change
@@ -1,77 +1,133 @@
import { Client } from "@/api";
import { t } from "i18next";
import { useState, useContext, useEffect, useMemo } from "react";
import React, {
useState,
useContext,
useEffect,
useMemo,
useRef,
useImperativeHandle,
} from "react";
import { AppSettingsProviderContext } from "@renderer/context";
import { LoaderIcon } from "lucide-react";
import { STORAGE_WORKER_ENDPOINT } from "@/constants";
import { Button } from "@/renderer/components/ui";

export const NetworkState = () => {
const { apiUrl, EnjoyApp } = useContext(AppSettingsProviderContext);

let timeoutId: ReturnType<typeof setTimeout | null> = null;

const [apiConnected, setApiConnected] = useState(false);
const [apiConnecting, setApiConnecting] = useState(false);
const [apiConnectError, setApiConnectError] = useState(false);
const [apiConnectTime, setApiConnectTime] = useState<number>(null);

const [ipInfoError, setIpInfoError] = useState(false);
const [ipInfo, setIpInfo] = useState(null);

const [platformInfo, setPlatformInfo] = useState<PlatformInfo>(null);
const [refreshing, setRefreshing] = useState(false);
const apiStateRef = useRef(null);
const storeageStateRef = useRef(null);
const ipStateRef = useRef(null);

const items = useMemo(() => {
const apiStateColor =
apiConnectTime < 200
? "text-green-500"
: apiConnectTime < 800
? "text-yellow-500"
: "text-red-500";

return [
{
title: t("apiConnectTime", { apiUrl }),
loading: !apiConnected && apiConnecting,
error: apiConnectError,
value: <span className={apiStateColor}>{apiConnectTime}ms</span>,
ref: apiStateRef,
refresh: true,
action: () => getConnectDelayTime(apiUrl + "/up"),
},
{
title: t("storageConnectTime"),
refresh: true,
ref: storeageStateRef,
action: () => getConnectDelayTime(STORAGE_WORKER_ENDPOINT),
},
{
title: t("ipInfo"),
loading: false,
error: ipInfoError,
value: (
<span>
{ipInfo
? `${ipInfo.ip} (${ipInfo.city}, ${ipInfo.country_name})`
: "-"}
</span>
),
ref: ipStateRef,
refresh: true,
action: async () =>
await fetch("https://ipapi.co/json")
.then((resp) => resp.json())
.then((info) => ({
text: `${info.ip} (${info.city}, ${info.country_name})`,
})),
},
{
title: t("platformInfo"),
loading: false,
error: false,
value: (
<span>
{platformInfo
? `${platformInfo.platform} ${platformInfo.arch} ${platformInfo.version}`
: "-"}
</span>
),
refresh: false,
action: async () =>
await EnjoyApp.app.getPlatformInfo().then((info) => ({
text: `${info.platform} ${info.arch} ${info.version}`,
})),
},
];
}, [
apiConnectTime,
apiConnecting,
apiConnected,
apiConnectError,
ipInfo,
ipInfoError,
platformInfo,
]);
}, [apiUrl]);

async function handleRefresh() {
if (refreshing) return;
setRefreshing(true);

await Promise.all([
apiStateRef?.current.getConnectState({ force: true }),
storeageStateRef?.current.getConnectState({ force: true }),
ipStateRef?.current.getConnectState({ force: true }),
]);

setRefreshing(false);
}

async function getConnectDelayTime(url: string) {
const startTime = new Date().getTime();
await fetch(url);
const duration = new Date().getTime() - startTime;

return {
color:
duration < 200
? "text-green-500"
: duration < 800
? "text-yellow-500"
: "text-red-500",
text: `${duration}ms`,
};
}

return (
<div className="py-4">
<div className="flex items-start justify-between">
<div className="mb-2">{t("networkState")}</div>
<Button variant="secondary" size="sm" onClick={handleRefresh}>
{t("refresh")}
</Button>
</div>

<div>
{items.map((item, index) => (
<NetworkStateItem key={index} {...item} />
))}
</div>
</div>
);
};

const NetworkStateItem = React.forwardRef(function (
{
title,
action,
refresh,
}: {
title: string;
refresh: boolean;
action: () => Promise<{ color?: string; text: string }>;
},
ref
) {
useImperativeHandle(ref, () => {
return { getConnectState };
});

let timeoutId: ReturnType<typeof setTimeout | null> = null;

const [connected, setConnected] = useState(false);
const [connecting, setConnecting] = useState(false);
const [connectError, setConnectError] = useState(false);
const [color, setColor] = useState("");
const [text, setText] = useState("");

useEffect(() => {
pollingAction();
getPlatformInfo();
startPolling();

return () => {
if (timeoutId) {
Expand All @@ -80,71 +136,45 @@ export const NetworkState = () => {
};
}, []);

async function pollingAction() {
await Promise.all([getApiConnectTime(), getIpInfo()]);

timeoutId = setTimeout(() => pollingAction(), 10000);
}

async function getApiConnectTime() {
setApiConnecting(true);
try {
const client = new Client({ baseUrl: apiUrl });
const startTime = new Date().getTime();
await client.up();
const endTime = new Date().getTime();
async function startPolling() {
await getConnectState();

setApiConnectTime(endTime - startTime);
setApiConnected(true);
} catch (error) {
setApiConnectError(true);
setApiConnected(false);
if (refresh) {
timeoutId = setTimeout(() => startPolling(), 10000);
}
setApiConnecting(false);
}

async function getIpInfo() {
async function getConnectState(
{ force }: { force?: boolean } = { force: false }
) {
if (force) setConnected(false);

setConnecting(true);
try {
await fetch("https://ipapi.co/json")
.then((resp) => resp.json())
.then((info) => setIpInfo(info));
const { color, text } = await action();

setColor(color);
setText(text);
setConnected(true);
} catch (error) {
setIpInfoError(true);
setConnectError(true);
setConnected(false);
}
}

async function getPlatformInfo() {
const info = await EnjoyApp.app.getPlatformInfo();
setPlatformInfo(info);
setConnecting(false);
}

return (
<div className="py-4">
<div className="flex items-start justify-between">
<div className="mb-2">{t("networkState")}</div>
</div>

<div>
{items.map((item, index) => {
return (
<div
key={index}
className="text-sm text-muted-foreground flex justify-between my-2"
>
<div className="">{item.title}</div>
<div className="">
{item.loading ? (
<LoaderIcon className="w-4 h-4 animate-spin" />
) : item.error ? (
<span className="text-red-500">{t("connectError")}</span>
) : (
item.value
)}
</div>
</div>
);
})}
<div className="text-sm text-muted-foreground flex justify-between my-2">
<div className="">{title}</div>
<div className="">
{!connected && connecting ? (
<LoaderIcon className="w-4 h-4 animate-spin" />
) : connectError ? (
<span className="text-red-500">{t("connectError")}</span>
) : (
<span className={color}>{text}</span>
)}
</div>
</div>
);
};
});
Loading