mirror of
https://github.com/coaidev/coai.git
synced 2025-05-21 14:00:13 +09:00
feat: optimize code syntax
This commit is contained in:
parent
9c6b11cbc6
commit
4fe8cb4574
@ -205,7 +205,7 @@ export const ChannelInfos: Record<string, ChannelInfo> = {
|
||||
"> 密钥即为 *mj-api-secret* (如果没有设置 secret 请填 `null` ) \n" +
|
||||
"> 白名单即为 *white-list*(如果没有回调 IP 白名单默认接收所有 IP 的回调,不需要加 | 以及后面的内容) \n" +
|
||||
"> 接入点填写你的 Midjourney Proxy 的部署地址,如 *http://localhost:8080*, *https://example.com* \n" +
|
||||
"> 注意:**请在系统设置中设置后端的公网 IP / 域名,否则无法接收回调**",
|
||||
"> 注意:**请在系统设置中设置后端的公网 IP / 域名,否则无法接收回调报错 please provide available notify url** \n",
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -84,6 +84,14 @@
|
||||
top: -34px;
|
||||
right: 0;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
transition: 0.25s ease;
|
||||
|
||||
&:hover {
|
||||
p, svg {
|
||||
color: hsl(var(--text-dark));
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
color: hsl(var(--text-secondary-dark));
|
||||
@ -91,16 +99,12 @@
|
||||
line-height: 1;
|
||||
margin: 0 0 0 6px;
|
||||
padding: 0;
|
||||
transition: 0.25s ease;
|
||||
}
|
||||
|
||||
svg {
|
||||
cursor: pointer;
|
||||
color: hsl(var(--text-secondary-dark));
|
||||
transition: .2s;
|
||||
|
||||
&:hover {
|
||||
color: hsl(var(--text-dark));
|
||||
}
|
||||
transition: 0.25s ease;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import { openDialog as openSubscriptionDialog } from "@/store/subscription.ts";
|
||||
import { AppDispatch } from "@/store";
|
||||
import {
|
||||
CalendarPlus,
|
||||
Check,
|
||||
Cloud,
|
||||
CloudCog,
|
||||
Cloudy,
|
||||
@ -31,7 +32,6 @@ import {
|
||||
Youtube,
|
||||
} from "lucide-react";
|
||||
import { copyClipboard } from "@/utils/dom.ts";
|
||||
import { useToast } from "./ui/use-toast.ts";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { parseProgressbar } from "@/components/plugins/progress.tsx";
|
||||
import { cn } from "@/components/ui/lib/utils.ts";
|
||||
@ -49,6 +49,8 @@ import { DialogClose } from "@radix-ui/react-dialog";
|
||||
import { appLogo } from "@/conf/env.ts";
|
||||
import { subscriptionDataSelector } from "@/store/globals.ts";
|
||||
import { useMessageActions } from "@/store/chat.ts";
|
||||
import { useTemporaryState } from "@/utils/hook.ts";
|
||||
import Icon from "@/components/utils/Icon.tsx";
|
||||
|
||||
type MarkdownProps = {
|
||||
children: string;
|
||||
@ -115,9 +117,10 @@ function MarkdownContent({
|
||||
}: MarkdownProps) {
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const { send: sendAction } = useMessageActions();
|
||||
|
||||
const { state, triggerState } = useTemporaryState();
|
||||
|
||||
const subscription = useSelector(subscriptionDataSelector);
|
||||
|
||||
useEffect(() => {
|
||||
@ -274,22 +277,30 @@ function MarkdownContent({
|
||||
},
|
||||
code({ inline, className, children, ...props }) {
|
||||
const match = /language-(\w+)/.exec(className || "");
|
||||
const language = match ? match[1].toLowerCase() : "";
|
||||
const language = match ? match[1].toLowerCase() : "unknown";
|
||||
if (language === "file") return parseFile(children.toString());
|
||||
if (language === "progress")
|
||||
return parseProgressbar(children.toString());
|
||||
|
||||
return !inline && match ? (
|
||||
if (inline)
|
||||
return (
|
||||
<code className={cn("code-inline", className)} {...props}>
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={`markdown-syntax`}>
|
||||
<div className={`markdown-syntax-header`}>
|
||||
<Copy
|
||||
className={`h-3 w-3`}
|
||||
<div
|
||||
className={`markdown-syntax-header`}
|
||||
onClick={async () => {
|
||||
await copyClipboard(children.toString());
|
||||
toast({
|
||||
title: t("share.copied"),
|
||||
});
|
||||
triggerState();
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
icon={state ? <Check /> : <Copy />}
|
||||
className={`h-3 w-3`}
|
||||
/>
|
||||
<p>{language}</p>
|
||||
</div>
|
||||
@ -304,10 +315,6 @@ function MarkdownContent({
|
||||
className={cn("code-block", codeStyle)}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<code className={cn("code-inline", className)} {...props}>
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
},
|
||||
}}
|
||||
|
@ -66,7 +66,7 @@ function MessageSegment(props: MessageProps) {
|
||||
return;
|
||||
props.onFocusLeave && props.onFocusLeave(event);
|
||||
} catch (e) {
|
||||
console.debug(e);
|
||||
console.debug(`[message] cannot leave focus: ${e}`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
@ -2,6 +2,7 @@ import { Button } from "./ui/button.tsx";
|
||||
import { useConversationActions, useMessages } from "@/store/chat.ts";
|
||||
import { MessageSquarePlus } from "lucide-react";
|
||||
import Github from "@/components/ui/icons/Github.tsx";
|
||||
import { openWindow } from "@/utils/device.ts";
|
||||
|
||||
function ProjectLink() {
|
||||
const messages = useMessages();
|
||||
@ -21,7 +22,7 @@ function ProjectLink() {
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() =>
|
||||
window.open("https://github.com/Deeptrain-Community/chatnio")
|
||||
openWindow("https://github.com/Deeptrain-Community/chatnio")
|
||||
}
|
||||
>
|
||||
<Github className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100" />
|
||||
|
@ -399,7 +399,6 @@ function UserTable() {
|
||||
<TableHead>{t("admin.is-subscribed")}</TableHead>
|
||||
<TableHead>{t("admin.level")}</TableHead>
|
||||
<TableHead>{t("admin.total-month")}</TableHead>
|
||||
{useDeeptrain && <TableHead>{t("admin.enterprise")}</TableHead>}
|
||||
<TableHead>{t("admin.is-banned")}</TableHead>
|
||||
<TableHead>{t("admin.is-admin")}</TableHead>
|
||||
<TableHead>{t("admin.action")}</TableHead>
|
||||
@ -422,9 +421,6 @@ function UserTable() {
|
||||
{t(`admin.identity.${userTypeArray[user.level]}`)}
|
||||
</TableCell>
|
||||
<TableCell>{user.total_month}</TableCell>
|
||||
{useDeeptrain && (
|
||||
<TableCell>{t(user.enterprise.toString())}</TableCell>
|
||||
)}
|
||||
<TableCell>{t(user.is_banned.toString())}</TableCell>
|
||||
<TableCell>{t(user.is_admin.toString())}</TableCell>
|
||||
<TableCell>
|
||||
|
@ -20,6 +20,7 @@ import {
|
||||
ListStart,
|
||||
Plug,
|
||||
Shield,
|
||||
User,
|
||||
} from "lucide-react";
|
||||
import { openDialog as openSub } from "@/store/subscription.ts";
|
||||
import { openDialog as openPackageDialog } from "@/store/package.ts";
|
||||
@ -27,9 +28,11 @@ import { openDialog as openInvitationDialog } from "@/store/invitation.ts";
|
||||
import { openDialog as openSharingDialog } from "@/store/sharing.ts";
|
||||
import { openDialog as openApiDialog } from "@/store/api.ts";
|
||||
import router from "@/router.tsx";
|
||||
import { useDeeptrain } from "@/conf/env.ts";
|
||||
import { deeptrainEndpoint, useDeeptrain } from "@/conf/env.ts";
|
||||
import React from "react";
|
||||
import { subscriptionDataSelector } from "@/store/globals.ts";
|
||||
import { openWindow } from "@/utils/device.ts";
|
||||
import { DeeptrainOnly } from "@/conf/deeptrain.tsx";
|
||||
|
||||
type MenuBarProps = {
|
||||
children: React.ReactNode;
|
||||
@ -70,12 +73,12 @@ function MenuBar({ children, className }: MenuBarProps) {
|
||||
{t("sub.title")}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{useDeeptrain && (
|
||||
<DeeptrainOnly>
|
||||
<DropdownMenuItem onClick={() => dispatch(openPackageDialog())}>
|
||||
<Boxes className={`h-4 w-4 mr-1`} />
|
||||
{t("pkg.title")}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</DeeptrainOnly>
|
||||
<DropdownMenuItem onClick={() => dispatch(openInvitationDialog())}>
|
||||
<Gift className={`h-4 w-4 mr-1`} />
|
||||
{t("invitation.invitation")}
|
||||
@ -88,6 +91,14 @@ function MenuBar({ children, className }: MenuBarProps) {
|
||||
<Plug className={`h-4 w-4 mr-1`} />
|
||||
{t("api.title")}
|
||||
</DropdownMenuItem>
|
||||
<DeeptrainOnly>
|
||||
<DropdownMenuItem
|
||||
onClick={() => openWindow(`${deeptrainEndpoint}/home`)}
|
||||
>
|
||||
<User className={`h-4 w-4 mr-1`} />
|
||||
{t("my-account")}
|
||||
</DropdownMenuItem>
|
||||
</DeeptrainOnly>
|
||||
{admin && (
|
||||
<DropdownMenuItem onClick={() => router.navigate("/admin")}>
|
||||
<Shield className={`h-4 w-4 mr-1`} />
|
||||
|
@ -14,7 +14,7 @@ import { useToast } from "@/components/ui/use-toast.ts";
|
||||
import { extractMessage, filterMessage } from "@/utils/processor.ts";
|
||||
import { copyClipboard } from "@/utils/dom.ts";
|
||||
import { useEffectAsync, useAnimation } from "@/utils/hook.ts";
|
||||
import { mobile } from "@/utils/device.ts";
|
||||
import { mobile, openWindow } from "@/utils/device.ts";
|
||||
import { Button } from "@/components/ui/button.tsx";
|
||||
import { selectMenu, setMenu } from "@/store/menu.ts";
|
||||
import {
|
||||
@ -314,7 +314,7 @@ function SidebarConversationList({
|
||||
onClick={async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
window.open(shared, "_blank");
|
||||
openWindow(shared, "_blank");
|
||||
}}
|
||||
>
|
||||
{t("share.view")}
|
||||
|
@ -25,7 +25,7 @@ function ScrollAction(
|
||||
const position = target.scrollTop + target.clientHeight;
|
||||
const height = target.scrollHeight;
|
||||
const diff = Math.abs(position - height);
|
||||
setVisibility(diff > 20);
|
||||
setVisibility(diff > 50);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -30,6 +30,8 @@ import { openDialog, quotaSelector } from "@/store/quota.ts";
|
||||
import { getPlanPrice } from "@/conf/subscription.tsx";
|
||||
import { Plans } from "@/api/types.tsx";
|
||||
import { subscriptionDataSelector } from "@/store/globals.ts";
|
||||
import { openWindow } from "@/utils/device.ts";
|
||||
import { DeeptrainOnly } from "@/conf/deeptrain.tsx";
|
||||
|
||||
function countPrice(data: Plans, base: number, month: number): number {
|
||||
const price = getPlanPrice(data, base) * month;
|
||||
@ -100,7 +102,7 @@ async function callBuyAction(
|
||||
|
||||
useDeeptrain
|
||||
? setTimeout(() => {
|
||||
window.open(`${deeptrainEndpoint}/home/wallet`);
|
||||
openWindow(`${deeptrainEndpoint}/home/wallet`);
|
||||
}, 2000)
|
||||
: dispatch(openDialog());
|
||||
}
|
||||
@ -186,7 +188,7 @@ export function Upgrade({ level, current }: UpgradeProps) {
|
||||
price: countPrice(subscriptionData, level, month).toFixed(2),
|
||||
})}
|
||||
|
||||
{useDeeptrain && (
|
||||
<DeeptrainOnly>
|
||||
<span className={`tax`}>
|
||||
(
|
||||
{t("sub.price-tax", {
|
||||
@ -196,7 +198,7 @@ export function Upgrade({ level, current }: UpgradeProps) {
|
||||
})}
|
||||
)
|
||||
</span>
|
||||
)}
|
||||
</DeeptrainOnly>
|
||||
</p>
|
||||
</div>
|
||||
<DialogFooter className={`translate-y-1.5`}>
|
||||
|
@ -1,8 +0,0 @@
|
||||
import { deeptrainAppName, deeptrainEndpoint } from "@/conf/env.ts";
|
||||
import { dev } from "@/conf/bootstrap.ts";
|
||||
|
||||
export function goDeepLogin() {
|
||||
location.href = `${deeptrainEndpoint}/login?app=${
|
||||
dev ? "dev" : deeptrainAppName
|
||||
}`;
|
||||
}
|
17
app/src/conf/deeptrain.tsx
Normal file
17
app/src/conf/deeptrain.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import {
|
||||
deeptrainAppName,
|
||||
deeptrainEndpoint,
|
||||
useDeeptrain,
|
||||
} from "@/conf/env.ts";
|
||||
import { dev } from "@/conf/bootstrap.ts";
|
||||
import React from "react";
|
||||
|
||||
export function goDeepLogin() {
|
||||
location.href = `${deeptrainEndpoint}/login?app=${
|
||||
dev ? "dev" : deeptrainAppName
|
||||
}`;
|
||||
}
|
||||
|
||||
export function DeeptrainOnly({ children }: { children: React.ReactNode }) {
|
||||
return useDeeptrain ? <>{children}</> : null;
|
||||
}
|
@ -24,6 +24,7 @@ import { Badge } from "@/components/ui/badge.tsx";
|
||||
import { useEffectAsync } from "@/utils/hook.ts";
|
||||
import { selectAuthenticated } from "@/store/auth.ts";
|
||||
import { deeptrainEndpoint, useDeeptrain } from "@/conf/env.ts";
|
||||
import { openWindow } from "@/utils/device.ts";
|
||||
|
||||
function PackageDialog() {
|
||||
const { t } = useTranslation();
|
||||
@ -79,7 +80,7 @@ function PackageDialog() {
|
||||
</Button>
|
||||
<Button
|
||||
variant={`default`}
|
||||
onClick={() => window.open(`${deeptrainEndpoint}/home/package`)}
|
||||
onClick={() => openWindow(`${deeptrainEndpoint}/home/package`)}
|
||||
>
|
||||
{t("pkg.go")}
|
||||
</Button>
|
||||
|
@ -48,6 +48,7 @@ import {
|
||||
import { useRedeem } from "@/api/redeem.ts";
|
||||
import { cn } from "@/components/ui/lib/utils.ts";
|
||||
import { subscriptionDataSelector } from "@/store/globals.ts";
|
||||
import { openWindow } from "@/utils/device.ts";
|
||||
|
||||
type AmountComponentProps = {
|
||||
amount: number;
|
||||
@ -240,7 +241,7 @@ function QuotaDialog() {
|
||||
<AlertDialogAction
|
||||
onClick={async () => {
|
||||
if (!useDeeptrain) {
|
||||
window.open(
|
||||
openWindow(
|
||||
`${buyLink}?quota=${amount}`,
|
||||
"_blank",
|
||||
);
|
||||
@ -274,7 +275,7 @@ function QuotaDialog() {
|
||||
});
|
||||
useDeeptrain &&
|
||||
setTimeout(() => {
|
||||
window.open(
|
||||
openWindow(
|
||||
`${deeptrainEndpoint}/home/wallet`,
|
||||
);
|
||||
}, 2000);
|
||||
|
@ -37,6 +37,7 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu.tsx";
|
||||
import { getSharedLink, SharingPreviewForm } from "@/api/sharing.ts";
|
||||
import { openWindow } from "@/utils/device.ts";
|
||||
|
||||
type ShareTableProps = {
|
||||
data: SharingPreviewForm[];
|
||||
@ -79,7 +80,7 @@ function ShareTable({ data }: ShareTableProps) {
|
||||
<DropdownMenuContent align={`center`}>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
window.open(getSharedLink(row.hash), "_blank");
|
||||
openWindow(getSharedLink(row.hash), "_blank");
|
||||
}}
|
||||
>
|
||||
<Eye className={`h-4 w-4 mr-1`} />
|
||||
|
@ -36,7 +36,7 @@
|
||||
"broadcast": "公告",
|
||||
"fatal": "应用崩溃",
|
||||
"download-fatal-log": "下载错误日志",
|
||||
"fatal-tips": "请您先检查您的网络,浏览器兼容性,尝试清除浏览器缓存并刷新页面。如果问题仍然存在,请将日志提供给开发者以便我们排查问题。",
|
||||
"fatal-tips": "请您先检查您的网络,浏览器兼容性,尝试清除浏览器缓存并刷新页面。如果问题仍然存在,请下载日志并提供完整复现步骤以给开发者以便我们排查问题。",
|
||||
"request-error": "请求失败,原因:{{reason}}",
|
||||
"delete": "删除",
|
||||
"remove": "移除",
|
||||
@ -52,6 +52,7 @@
|
||||
"min-quota": "最低余额",
|
||||
"your-quota": "您的余额",
|
||||
"title": "标题",
|
||||
"my-account": "我的账户",
|
||||
"auth": {
|
||||
"username": "用户名",
|
||||
"username-placeholder": "请输入用户名",
|
||||
|
@ -33,7 +33,7 @@
|
||||
"broadcast": "Broadcast",
|
||||
"fatal": "App crashed",
|
||||
"download-fatal-log": "Download error log",
|
||||
"fatal-tips": "Please try to check your network, browser compatibility, try to clear the browser cache and refresh the page. If the problem still exists, please provide the log to the developer so that we can troubleshoot the problem.",
|
||||
"fatal-tips": "Please check your web, browser compatibility first, try clearing your browser cache and refreshing the page. If the problem persists, please download the log and provide the complete reproduction steps to the developer so we can troubleshoot the issue.",
|
||||
"tag": {
|
||||
"free": "Free",
|
||||
"official": "Official",
|
||||
@ -753,5 +753,6 @@
|
||||
"model": "Model",
|
||||
"min-quota": "Minimum Balance",
|
||||
"your-quota": "Your balance",
|
||||
"title": "Title"
|
||||
"title": "Title",
|
||||
"my-account": "My Account"
|
||||
}
|
@ -33,7 +33,7 @@
|
||||
"broadcast": "公告",
|
||||
"fatal": "アプリのクラッシュ",
|
||||
"download-fatal-log": "エラーログのダウンロード",
|
||||
"fatal-tips": "まずウェブとブラウザの互換性を確認し、ブラウザのキャッシュをクリアしてページを更新してみてください。問題が解決しない場合は、問題のトラブルシューティングを行うために、開発者にログを提供してください。",
|
||||
"fatal-tips": "まずウェブとブラウザの互換性を確認し、ブラウザのキャッシュをクリアしてページを更新してみてください。問題が解決しない場合は、ログをダウンロードして完全な再現手順を開発者に提供し、問題のトラブルシューティングを行います。",
|
||||
"tag": {
|
||||
"free": "無料",
|
||||
"official": "広汽Hondaが",
|
||||
@ -753,5 +753,6 @@
|
||||
"model": "モデル",
|
||||
"min-quota": "最低残高",
|
||||
"your-quota": "残高",
|
||||
"title": "タイトル"
|
||||
"title": "タイトル",
|
||||
"my-account": "マイアカウント"
|
||||
}
|
@ -33,7 +33,7 @@
|
||||
"broadcast": "Объявление",
|
||||
"fatal": "Приложение вылетело",
|
||||
"download-fatal-log": "Скачать журнал ошибок",
|
||||
"fatal-tips": "Пожалуйста, попробуйте проверить свою сеть, совместимость браузера, попробуйте очистить кэш браузера и обновить страницу. Если проблема все еще существует, пожалуйста, предоставьте журнал разработчику, чтобы мы могли устранить проблему.",
|
||||
"fatal-tips": "Сначала проверьте совместимость веб-браузера, попробуйте очистить кэш браузера и обновить страницу. Если проблема не устранена, загрузите журнал и предоставьте разработчику полные инструкции по воспроизведению, чтобы мы могли устранить проблему.",
|
||||
"tag": {
|
||||
"free": "Бесплатно",
|
||||
"official": "Официальный",
|
||||
@ -753,5 +753,6 @@
|
||||
"model": "модель",
|
||||
"min-quota": "Минимальный баланс",
|
||||
"your-quota": "Ваш баланс",
|
||||
"title": "заглавие"
|
||||
"title": "заглавие",
|
||||
"my-account": "Моя учетная запись"
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import router from "@/router.tsx";
|
||||
import { useDeeptrain } from "@/conf/env.ts";
|
||||
import { goDeepLogin } from "@/conf/deeptrain.ts";
|
||||
import { goDeepLogin } from "@/conf/deeptrain.tsx";
|
||||
|
||||
export let event: BeforeInstallPromptEvent | undefined;
|
||||
|
||||
|
@ -40,3 +40,18 @@ export function useMobile(): boolean {
|
||||
|
||||
return mobile;
|
||||
}
|
||||
|
||||
export function openWindow(url: string, target?: string): void {
|
||||
/**
|
||||
* Open a new window with the given URL.
|
||||
* If the device does not support opening a new window, the URL will be opened in the current window.
|
||||
* @param url The URL to open.
|
||||
* @param target The target of the URL.
|
||||
*/
|
||||
|
||||
if (mobile) {
|
||||
window.location.href = url;
|
||||
} else {
|
||||
window.open(url, target);
|
||||
}
|
||||
}
|
||||
|
@ -321,22 +321,8 @@ export function isContainDom(
|
||||
*/
|
||||
|
||||
if (!el || !target) return false;
|
||||
return el.contains(target) && (!notIncludeSelf || el !== target);
|
||||
if (!notIncludeSelf) {
|
||||
return el.contains(target);
|
||||
}
|
||||
|
||||
export function isContainEventTarget(
|
||||
el: HTMLElement | undefined | null,
|
||||
e: Event,
|
||||
) {
|
||||
/**
|
||||
* Test if element contains event target
|
||||
* @param el Element
|
||||
* @param e Event
|
||||
* @example
|
||||
* const el = document.getElementById("el");
|
||||
* const handler = (e: Event) => console.log(isContainEventTarget(el, e));
|
||||
* el.addEventListener("click", handler);
|
||||
*/
|
||||
|
||||
return isContainDom(el, e.target as HTMLElement);
|
||||
return el === target || el.contains(target);
|
||||
}
|
||||
|
@ -44,36 +44,15 @@ export function useAnimation(
|
||||
};
|
||||
}
|
||||
|
||||
export function useShared<T>(): {
|
||||
hook: (v: T) => void;
|
||||
useHook: () => Promise<T>;
|
||||
export function useTemporaryState(interval?: number): {
|
||||
state: boolean;
|
||||
triggerState: () => void;
|
||||
} {
|
||||
/**
|
||||
* Share value between components, useful for sharing data between components / redux dispatches
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* const dispatch = useDispatch();
|
||||
* const { hook, useHook } = useShared<string>();
|
||||
*
|
||||
* dispatch(updateMigration({ hook }));
|
||||
* const response = await useHook();
|
||||
*/
|
||||
let value: T | undefined = undefined;
|
||||
const [stamp, setStamp] = React.useState<number>(0);
|
||||
const triggerState = () => setStamp(new Date().getTime());
|
||||
|
||||
return {
|
||||
hook: (v: T) => {
|
||||
value = v;
|
||||
},
|
||||
useHook: () => {
|
||||
return new Promise<T>((resolve) => {
|
||||
if (value) return resolve(value);
|
||||
const interval = setInterval(() => {
|
||||
if (value) {
|
||||
clearInterval(interval);
|
||||
resolve(value);
|
||||
}
|
||||
}, 50);
|
||||
});
|
||||
},
|
||||
state: Date.now() - stamp < (interval ?? 3000),
|
||||
triggerState,
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user