mirror of
https://github.com/sqlchat/sqlchat.git
synced 2025-09-28 02:24:49 +08:00
feat: implement Dialog kit component
This commit is contained in:
@ -13,6 +13,7 @@
|
|||||||
"@emotion/styled": "^11.10.6",
|
"@emotion/styled": "^11.10.6",
|
||||||
"@mui/material": "^5.11.14",
|
"@mui/material": "^5.11.14",
|
||||||
"@mui/styled-engine-sc": "^5.11.11",
|
"@mui/styled-engine-sc": "^5.11.11",
|
||||||
|
"@radix-ui/react-dialog": "^1.0.3",
|
||||||
"@radix-ui/react-select": "^1.2.1",
|
"@radix-ui/react-select": "^1.2.1",
|
||||||
"@radix-ui/react-tooltip": "^1.0.5",
|
"@radix-ui/react-tooltip": "^1.0.5",
|
||||||
"@vercel/analytics": "^0.1.11",
|
"@vercel/analytics": "^0.1.11",
|
||||||
|
30
pnpm-lock.yaml
generated
30
pnpm-lock.yaml
generated
@ -13,6 +13,9 @@ dependencies:
|
|||||||
'@mui/styled-engine-sc':
|
'@mui/styled-engine-sc':
|
||||||
specifier: ^5.11.11
|
specifier: ^5.11.11
|
||||||
version: 5.11.11(styled-components@5.3.9)
|
version: 5.11.11(styled-components@5.3.9)
|
||||||
|
'@radix-ui/react-dialog':
|
||||||
|
specifier: ^1.0.3
|
||||||
|
version: 1.0.3(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0)
|
||||||
'@radix-ui/react-select':
|
'@radix-ui/react-select':
|
||||||
specifier: ^1.2.1
|
specifier: ^1.2.1
|
||||||
version: 1.2.1(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0)
|
version: 1.2.1(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0)
|
||||||
@ -876,6 +879,33 @@ packages:
|
|||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-dialog@1.0.3(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-owNhq36kNPqC2/a+zJRioPg6HHnTn5B/sh/NjTY8r4W9g1L5VJlrzZIVcBr7R9Mg8iLjVmh6MGgMlfoVf/WO/A==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.21.0
|
||||||
|
'@radix-ui/primitive': 1.0.0
|
||||||
|
'@radix-ui/react-compose-refs': 1.0.0(react@18.2.0)
|
||||||
|
'@radix-ui/react-context': 1.0.0(react@18.2.0)
|
||||||
|
'@radix-ui/react-dismissable-layer': 1.0.3(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@radix-ui/react-focus-guards': 1.0.0(react@18.2.0)
|
||||||
|
'@radix-ui/react-focus-scope': 1.0.2(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@radix-ui/react-id': 1.0.0(react@18.2.0)
|
||||||
|
'@radix-ui/react-portal': 1.0.2(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@radix-ui/react-presence': 1.0.0(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@radix-ui/react-primitive': 1.0.2(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@radix-ui/react-slot': 1.0.1(react@18.2.0)
|
||||||
|
'@radix-ui/react-use-controllable-state': 1.0.0(react@18.2.0)
|
||||||
|
aria-hidden: 1.2.3
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
|
react-remove-scroll: 2.5.5(@types/react@18.0.28)(react@18.2.0)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/react'
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@radix-ui/react-direction@1.0.0(react@18.2.0):
|
/@radix-ui/react-direction@1.0.0(react@18.2.0):
|
||||||
resolution: {integrity: sha512-2HV05lGUgYcA6xgLQ4BKPDmtL+QbIZYH5fCOTAOOcJ5O0QbWS3i9lKaurLzliYUDhORI2Qr3pyjhJh44lKA3rQ==}
|
resolution: {integrity: sha512-2HV05lGUgYcA6xgLQ4BKPDmtL+QbIZYH5fCOTAOOcJ5O0QbWS3i9lKaurLzliYUDhORI2Qr3pyjhJh44lKA3rQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import Icon from "./Icon";
|
import Dialog from "./kit/Dialog";
|
||||||
|
|
||||||
export interface ActionConfirmModalProps {
|
export interface ActionConfirmModalProps {
|
||||||
title: string;
|
title: string;
|
||||||
@ -14,31 +14,25 @@ const ActionConfirmModal = (props: ActionConfirmModalProps) => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="modal modal-middle modal-open">
|
<Dialog title={title} onClose={close}>
|
||||||
<div className="modal-box relative">
|
<div className="w-full flex flex-col justify-start items-start mt-2">
|
||||||
<h3 className="font-bold text-lg">{title}</h3>
|
<p className="text-gray-500">{content}</p>
|
||||||
<button className="btn btn-sm btn-circle absolute right-4 top-4" onClick={close}>
|
|
||||||
<Icon.IoMdClose className="w-5 h-auto" />
|
|
||||||
</button>
|
|
||||||
<div className="w-full flex flex-col justify-start items-start space-y-3 pt-4">
|
|
||||||
<p className="text-gray-500">{content}</p>
|
|
||||||
</div>
|
|
||||||
<div className="modal-action">
|
|
||||||
<button className="btn btn-outline" onClick={close}>
|
|
||||||
{t("common.close")}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className={`btn ${confirmButtonStyle}`}
|
|
||||||
onClick={() => {
|
|
||||||
confirm();
|
|
||||||
close();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("common.confirm")}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="w-full flex flex-row justify-end items-center mt-4 space-x-2">
|
||||||
|
<button className="btn btn-outline" onClick={close}>
|
||||||
|
{t("common.close")}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`btn ${confirmButtonStyle}`}
|
||||||
|
onClick={() => {
|
||||||
|
confirm();
|
||||||
|
close();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("common.confirm")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { createPortal } from "react-dom";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import ClearDataConfirmModal from "./ClearDataConfirmModal";
|
import ClearDataConfirmModal from "./ClearDataConfirmModal";
|
||||||
|
|
||||||
@ -13,8 +12,7 @@ const ClearDataButton = () => {
|
|||||||
{t("common.clear")}
|
{t("common.clear")}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{showClearDataConfirmModal &&
|
{showClearDataConfirmModal && <ClearDataConfirmModal close={() => setShowClearDataConfirmModal(false)} />}
|
||||||
createPortal(<ClearDataConfirmModal close={() => setShowClearDataConfirmModal(false)} />, document.body)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import Icon from "./Icon";
|
import Dialog from "./kit/Dialog";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
close: () => void;
|
close: () => void;
|
||||||
@ -20,25 +20,19 @@ const ClearDataConfirmModal = (props: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="modal modal-middle modal-open">
|
<Dialog title="Clear all data" onClose={close}>
|
||||||
<div className="modal-box relative">
|
<div className="w-full flex flex-col justify-start items-start mt-2">
|
||||||
<h3 className="font-bold text-lg">Clear all data</h3>
|
<p className="text-gray-500">SQL Chat saves all your data in your local browser. Are you sure to clear all of them?</p>
|
||||||
<button className="btn btn-sm btn-circle absolute right-4 top-4" onClick={close}>
|
|
||||||
<Icon.IoMdClose className="w-5 h-auto" />
|
|
||||||
</button>
|
|
||||||
<div className="w-full flex flex-col justify-start items-start space-y-3 pt-4">
|
|
||||||
<p className="text-gray-500">SQL Chat saves all your data in your local browser. Are you sure to clear all of them?</p>
|
|
||||||
</div>
|
|
||||||
<div className="modal-action">
|
|
||||||
<button className="btn btn-outline" onClick={close}>
|
|
||||||
{t("common.close")}
|
|
||||||
</button>
|
|
||||||
<button className="btn btn-error" onClick={handleClearData}>
|
|
||||||
{t("common.clear")}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="w-full flex flex-row justify-end items-center mt-4 space-x-2">
|
||||||
|
<button className="btn btn-outline" onClick={close}>
|
||||||
|
{t("common.close")}
|
||||||
|
</button>
|
||||||
|
<button className="btn btn-error" onClick={handleClearData}>
|
||||||
|
{t("common.clear")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { Drawer } from "@mui/material";
|
import { Drawer } from "@mui/material";
|
||||||
import { head } from "lodash-es";
|
import { head } from "lodash-es";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { createPortal } from "react-dom";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useConversationStore, useConnectionStore, useLayoutStore, ResponsiveWidth } from "@/store";
|
import { useConversationStore, useConnectionStore, useLayoutStore, ResponsiveWidth } from "@/store";
|
||||||
import { Conversation, Connection } from "@/types";
|
import { Conversation, Connection } from "@/types";
|
||||||
@ -290,26 +289,18 @@ const ConnectionSidebar = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
||||||
{createPortal(
|
{state.showCreateConnectionModal && (
|
||||||
<CreateConnectionModal
|
<CreateConnectionModal connection={editConnectionModalContext} close={() => toggleCreateConnectionModal(false)} />
|
||||||
show={state.showCreateConnectionModal}
|
|
||||||
connection={editConnectionModalContext}
|
|
||||||
close={() => toggleCreateConnectionModal(false)}
|
|
||||||
/>,
|
|
||||||
document.body
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{createPortal(<SettingModal show={state.showSettingModal} close={() => toggleSettingModal(false)} />, document.body)}
|
{state.showSettingModal && <SettingModal close={() => toggleSettingModal(false)} />}
|
||||||
|
|
||||||
{state.showEditConversationTitleModal &&
|
{state.showEditConversationTitleModal && editConversationTitleModalContext && (
|
||||||
editConversationTitleModalContext &&
|
<EditConversationTitleModal
|
||||||
createPortal(
|
close={() => toggleEditConversationTitleModal(false)}
|
||||||
<EditConversationTitleModal
|
conversation={editConversationTitleModalContext}
|
||||||
close={() => toggleEditConversationTitleModal(false)}
|
/>
|
||||||
conversation={editConversationTitleModalContext}
|
)}
|
||||||
/>,
|
|
||||||
document.body
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
import { cloneDeep, head } from "lodash-es";
|
import { cloneDeep, head } from "lodash-es";
|
||||||
import { ChangeEvent, useEffect, useState } from "react";
|
import { ChangeEvent, useEffect, useState } from "react";
|
||||||
import { createPortal } from "react-dom";
|
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import TextareaAutosize from "react-textarea-autosize";
|
import TextareaAutosize from "react-textarea-autosize";
|
||||||
import { useConnectionStore } from "@/store";
|
import { useConnectionStore } from "@/store";
|
||||||
import { Connection, Engine, ResponseObject } from "@/types";
|
import { Connection, Engine, ResponseObject } from "@/types";
|
||||||
import Select from "./kit/Select";
|
import Select from "./kit/Select";
|
||||||
|
import TextField from "./kit/TextField";
|
||||||
|
import Dialog from "./kit/Dialog";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import DataStorageBanner from "./DataStorageBanner";
|
import DataStorageBanner from "./DataStorageBanner";
|
||||||
import ActionConfirmModal from "./ActionConfirmModal";
|
import ActionConfirmModal from "./ActionConfirmModal";
|
||||||
import TextField from "./kit/TextField";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
show: boolean;
|
|
||||||
connection?: Connection;
|
connection?: Connection;
|
||||||
close: () => void;
|
close: () => void;
|
||||||
}
|
}
|
||||||
@ -47,7 +46,7 @@ const defaultConnection: Connection = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const CreateConnectionModal = (props: Props) => {
|
const CreateConnectionModal = (props: Props) => {
|
||||||
const { show, connection: editConnection, close } = props;
|
const { connection: editConnection, close } = props;
|
||||||
const connectionStore = useConnectionStore();
|
const connectionStore = useConnectionStore();
|
||||||
const [connection, setConnection] = useState<Connection>(defaultConnection);
|
const [connection, setConnection] = useState<Connection>(defaultConnection);
|
||||||
const [showDeleteConnectionModal, setShowDeleteConnectionModal] = useState(false);
|
const [showDeleteConnectionModal, setShowDeleteConnectionModal] = useState(false);
|
||||||
@ -59,23 +58,21 @@ const CreateConnectionModal = (props: Props) => {
|
|||||||
const allowSave = connection.host !== "" && connection.username !== "";
|
const allowSave = connection.host !== "" && connection.username !== "";
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (show) {
|
const connection = isEditing ? editConnection : defaultConnection;
|
||||||
const connection = isEditing ? editConnection : defaultConnection;
|
setConnection(connection);
|
||||||
setConnection(connection);
|
if (connection.ssl) {
|
||||||
if (connection.ssl) {
|
if (connection.ssl.ca && connection.ssl.cert && connection.ssl.key) {
|
||||||
if (connection.ssl.ca && connection.ssl.cert && connection.ssl.key) {
|
setSSLType("full");
|
||||||
setSSLType("full");
|
|
||||||
} else {
|
|
||||||
setSSLType("ca-only");
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
setSSLType("none");
|
setSSLType("ca-only");
|
||||||
}
|
}
|
||||||
setSelectedSSLField("ca");
|
} else {
|
||||||
setIsRequesting(false);
|
setSSLType("none");
|
||||||
setShowDeleteConnectionModal(false);
|
|
||||||
}
|
}
|
||||||
}, [show]);
|
setSelectedSSLField("ca");
|
||||||
|
setIsRequesting(false);
|
||||||
|
setShowDeleteConnectionModal(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let ssl = undefined;
|
let ssl = undefined;
|
||||||
@ -209,164 +206,156 @@ const CreateConnectionModal = (props: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={`modal modal-middle ${show && "modal-open"}`}>
|
<Dialog title={isEditing ? "Edit Connection" : "Create Connection"} onClose={close}>
|
||||||
<div className="modal-box relative">
|
<div className="w-full flex flex-col justify-start items-start space-y-3 pt-4">
|
||||||
<h3 className="font-bold text-lg">{isEditing ? "Edit Connection" : "Create Connection"}</h3>
|
<DataStorageBanner className="rounded-lg bg-white border py-2 !justify-start" alwaysShow={true} />
|
||||||
<button className="btn btn-sm btn-circle absolute right-4 top-4" onClick={close}>
|
<div className="w-full flex flex-col">
|
||||||
<Icon.IoMdClose className="w-5 h-auto" />
|
<label className="block text-sm font-medium text-gray-700 mb-1">Database Type</label>
|
||||||
</button>
|
<Select
|
||||||
<div className="w-full flex flex-col justify-start items-start space-y-3 pt-4">
|
className="w-full"
|
||||||
<DataStorageBanner className="rounded-lg bg-white border py-2 !justify-start" alwaysShow={true} />
|
value={connection.engineType}
|
||||||
<div className="w-full flex flex-col">
|
itemList={[
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">Database Type</label>
|
{ value: Engine.MySQL, label: "MySQL" },
|
||||||
<Select
|
{ value: Engine.PostgreSQL, label: "PostgreSQL" },
|
||||||
className="w-full"
|
]}
|
||||||
value={connection.engineType}
|
onValueChange={(value) => setPartialConnection({ engineType: value as Engine })}
|
||||||
itemList={[
|
/>
|
||||||
{ value: Engine.MySQL, label: "MySQL" },
|
|
||||||
{ value: Engine.PostgreSQL, label: "PostgreSQL" },
|
|
||||||
]}
|
|
||||||
onValueChange={(value) => setPartialConnection({ engineType: value as Engine })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="w-full flex flex-col">
|
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">Host</label>
|
|
||||||
<TextField placeholder="Connect host" value={connection.host} onChange={(value) => setPartialConnection({ host: value })} />
|
|
||||||
</div>
|
|
||||||
<div className="w-full flex flex-col">
|
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">Port</label>
|
|
||||||
<TextField placeholder="Connect port" value={connection.port} onChange={(value) => setPartialConnection({ port: value })} />
|
|
||||||
</div>
|
|
||||||
{showDatabaseField && (
|
|
||||||
<div className="w-full flex flex-col">
|
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">Database Name</label>
|
|
||||||
<TextField
|
|
||||||
placeholder="Connect database"
|
|
||||||
value={connection.database || ""}
|
|
||||||
onChange={(value) => setPartialConnection({ database: value })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="w-full flex flex-col">
|
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">Username</label>
|
|
||||||
<TextField
|
|
||||||
placeholder="Connect username"
|
|
||||||
value={connection.username || ""}
|
|
||||||
onChange={(value) => setPartialConnection({ username: value })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="w-full flex flex-col">
|
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">Password</label>
|
|
||||||
<TextField
|
|
||||||
placeholder="Connect password"
|
|
||||||
value={connection.password || ""}
|
|
||||||
onChange={(value) => setPartialConnection({ password: value })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="w-full flex flex-col">
|
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">SSL</label>
|
|
||||||
<div className="w-full flex flex-row justify-start items-start flex-wrap">
|
|
||||||
{SSLTypeOptions.map((option) => (
|
|
||||||
<label key={option.value} className="w-auto flex flex-row justify-start items-center cursor-pointer mr-3 mb-2">
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
className="radio w-4 h-4 mr-1"
|
|
||||||
value={option.value}
|
|
||||||
checked={sslType === option.value}
|
|
||||||
onChange={(e) => setSSLType(e.target.value as SSLType)}
|
|
||||||
/>
|
|
||||||
<span className="text-sm">{option.label}</span>
|
|
||||||
</label>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
{sslType !== "none" && (
|
|
||||||
<>
|
|
||||||
<div className="text-sm space-x-3 mb-2">
|
|
||||||
<span
|
|
||||||
className={`leading-6 pb-1 border-b-2 border-transparent cursor-pointer opacity-60 hover:opacity-80 ${
|
|
||||||
selectedSSLField === "ca" && "!border-indigo-600 !opacity-100"
|
|
||||||
} `}
|
|
||||||
onClick={() => setSelectedSSLField("ca")}
|
|
||||||
>
|
|
||||||
CA Certificate
|
|
||||||
</span>
|
|
||||||
{sslType === "full" && (
|
|
||||||
<>
|
|
||||||
<span
|
|
||||||
className={`leading-6 pb-1 border-b-2 border-transparent cursor-pointer opacity-60 hover:opacity-80 ${
|
|
||||||
selectedSSLField === "key" && "!border-indigo-600 !opacity-100"
|
|
||||||
}`}
|
|
||||||
onClick={() => setSelectedSSLField("key")}
|
|
||||||
>
|
|
||||||
Client Key
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className={`leading-6 pb-1 border-b-2 border-transparent cursor-pointer opacity-60 hover:opacity-80 ${
|
|
||||||
selectedSSLField === "cert" && "!border-indigo-600 !opacity-100"
|
|
||||||
}`}
|
|
||||||
onClick={() => setSelectedSSLField("cert")}
|
|
||||||
>
|
|
||||||
Client Certificate
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="w-full h-auto relative">
|
|
||||||
<TextareaAutosize
|
|
||||||
className="w-full border resize-none rounded-lg text-sm p-3"
|
|
||||||
minRows={3}
|
|
||||||
maxRows={3}
|
|
||||||
value={(connection.ssl && connection.ssl[selectedSSLField]) ?? ""}
|
|
||||||
onChange={handleSSLValueChange}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className={`${
|
|
||||||
connection.ssl && connection.ssl[selectedSSLField] && "hidden"
|
|
||||||
} absolute top-3 left-4 text-gray-400 text-sm leading-6 pointer-events-none`}
|
|
||||||
>
|
|
||||||
<span className="">Input or </span>
|
|
||||||
<label className="pointer-events-auto border border-dashed px-2 py-1 rounded-lg cursor-pointer hover:border-gray-600 hover:text-gray-600">
|
|
||||||
upload file
|
|
||||||
<input className="hidden" type="file" onChange={handleSSLFileInputChange} />
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="modal-action w-full flex flex-row justify-between items-center space-x-2">
|
<div className="w-full flex flex-col">
|
||||||
<div>
|
<label className="block text-sm font-medium text-gray-700 mb-1">Host</label>
|
||||||
{isEditing && (
|
<TextField placeholder="Connect host" value={connection.host} onChange={(value) => setPartialConnection({ host: value })} />
|
||||||
<button className="btn btn-ghost" onClick={() => setShowDeleteConnectionModal(true)}>
|
</div>
|
||||||
Delete
|
<div className="w-full flex flex-col">
|
||||||
</button>
|
<label className="block text-sm font-medium text-gray-700 mb-1">Port</label>
|
||||||
)}
|
<TextField placeholder="Connect port" value={connection.port} onChange={(value) => setPartialConnection({ port: value })} />
|
||||||
|
</div>
|
||||||
|
{showDatabaseField && (
|
||||||
|
<div className="w-full flex flex-col">
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-1">Database Name</label>
|
||||||
|
<TextField
|
||||||
|
placeholder="Connect database"
|
||||||
|
value={connection.database || ""}
|
||||||
|
onChange={(value) => setPartialConnection({ database: value })}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-x-2 flex flex-row justify-center">
|
)}
|
||||||
<button className="btn btn-outline" onClick={close}>
|
<div className="w-full flex flex-col">
|
||||||
Close
|
<label className="block text-sm font-medium text-gray-700 mb-1">Username</label>
|
||||||
</button>
|
<TextField
|
||||||
<button className="btn" disabled={isRequesting || !allowSave} onClick={handleCreateConnection}>
|
placeholder="Connect username"
|
||||||
{isRequesting && <Icon.BiLoaderAlt className="w-4 h-auto animate-spin mr-1" />}
|
value={connection.username || ""}
|
||||||
Save
|
onChange={(value) => setPartialConnection({ username: value })}
|
||||||
</button>
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-full flex flex-col">
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-1">Password</label>
|
||||||
|
<TextField
|
||||||
|
placeholder="Connect password"
|
||||||
|
value={connection.password || ""}
|
||||||
|
onChange={(value) => setPartialConnection({ password: value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-full flex flex-col">
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-1">SSL</label>
|
||||||
|
<div className="w-full flex flex-row justify-start items-start flex-wrap">
|
||||||
|
{SSLTypeOptions.map((option) => (
|
||||||
|
<label key={option.value} className="w-auto flex flex-row justify-start items-center cursor-pointer mr-3 mb-2">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
className="radio w-4 h-4 mr-1"
|
||||||
|
value={option.value}
|
||||||
|
checked={sslType === option.value}
|
||||||
|
onChange={(e) => setSSLType(e.target.value as SSLType)}
|
||||||
|
/>
|
||||||
|
<span className="text-sm">{option.label}</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
{sslType !== "none" && (
|
||||||
|
<>
|
||||||
|
<div className="text-sm space-x-3 mb-2">
|
||||||
|
<span
|
||||||
|
className={`leading-6 pb-1 border-b-2 border-transparent cursor-pointer opacity-60 hover:opacity-80 ${
|
||||||
|
selectedSSLField === "ca" && "!border-indigo-600 !opacity-100"
|
||||||
|
} `}
|
||||||
|
onClick={() => setSelectedSSLField("ca")}
|
||||||
|
>
|
||||||
|
CA Certificate
|
||||||
|
</span>
|
||||||
|
{sslType === "full" && (
|
||||||
|
<>
|
||||||
|
<span
|
||||||
|
className={`leading-6 pb-1 border-b-2 border-transparent cursor-pointer opacity-60 hover:opacity-80 ${
|
||||||
|
selectedSSLField === "key" && "!border-indigo-600 !opacity-100"
|
||||||
|
}`}
|
||||||
|
onClick={() => setSelectedSSLField("key")}
|
||||||
|
>
|
||||||
|
Client Key
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className={`leading-6 pb-1 border-b-2 border-transparent cursor-pointer opacity-60 hover:opacity-80 ${
|
||||||
|
selectedSSLField === "cert" && "!border-indigo-600 !opacity-100"
|
||||||
|
}`}
|
||||||
|
onClick={() => setSelectedSSLField("cert")}
|
||||||
|
>
|
||||||
|
Client Certificate
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="w-full h-auto relative">
|
||||||
|
<TextareaAutosize
|
||||||
|
className="w-full border resize-none rounded-lg text-sm p-3"
|
||||||
|
minRows={3}
|
||||||
|
maxRows={3}
|
||||||
|
value={(connection.ssl && connection.ssl[selectedSSLField]) ?? ""}
|
||||||
|
onChange={handleSSLValueChange}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className={`${
|
||||||
|
connection.ssl && connection.ssl[selectedSSLField] && "hidden"
|
||||||
|
} absolute top-3 left-4 text-gray-400 text-sm leading-6 pointer-events-none`}
|
||||||
|
>
|
||||||
|
<span className="">Input or </span>
|
||||||
|
<label className="pointer-events-auto border border-dashed px-2 py-1 rounded-lg cursor-pointer hover:border-gray-600 hover:text-gray-600">
|
||||||
|
upload file
|
||||||
|
<input className="hidden" type="file" onChange={handleSSLFileInputChange} />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="modal-action w-full flex flex-row justify-between items-center space-x-2">
|
||||||
|
<div>
|
||||||
|
{isEditing && (
|
||||||
|
<button className="btn btn-ghost" onClick={() => setShowDeleteConnectionModal(true)}>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="space-x-2 flex flex-row justify-center">
|
||||||
|
<button className="btn btn-outline" onClick={close}>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
<button className="btn" disabled={isRequesting || !allowSave} onClick={handleCreateConnection}>
|
||||||
|
{isRequesting && <Icon.BiLoaderAlt className="w-4 h-auto animate-spin mr-1" />}
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
{showDeleteConnectionModal &&
|
{showDeleteConnectionModal && (
|
||||||
createPortal(
|
<ActionConfirmModal
|
||||||
<ActionConfirmModal
|
title="Delete Connection"
|
||||||
title="Delete Connection"
|
content="Are you sure you want to delete this connection?"
|
||||||
content="Are you sure you want to delete this connection?"
|
confirmButtonStyle="btn-error"
|
||||||
confirmButtonStyle="btn-error"
|
close={() => setShowDeleteConnectionModal(false)}
|
||||||
close={() => setShowDeleteConnectionModal(false)}
|
confirm={() => handleDeleteConnection()}
|
||||||
confirm={() => handleDeleteConnection()}
|
/>
|
||||||
/>,
|
)}
|
||||||
document.body
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -3,8 +3,8 @@ import { toast } from "react-hot-toast";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useConversationStore } from "@/store";
|
import { useConversationStore } from "@/store";
|
||||||
import { Conversation } from "@/types";
|
import { Conversation } from "@/types";
|
||||||
import Icon from "./Icon";
|
|
||||||
import TextField from "./kit/TextField";
|
import TextField from "./kit/TextField";
|
||||||
|
import Dialog from "./kit/Dialog";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
conversation: Conversation;
|
conversation: Conversation;
|
||||||
@ -32,25 +32,19 @@ const EditConversationTitleModal = (props: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="modal modal-middle modal-open">
|
<Dialog title={t("conversation.edit-title")} onClose={close}>
|
||||||
<div className="modal-box relative">
|
<div className="w-full flex flex-col justify-start items-start mt-2">
|
||||||
<h3 className="font-bold text-lg">{t("conversation.edit-title")}</h3>
|
<TextField placeholder={t("conversation.conversation-title") || ""} value={title} onChange={(value) => setTitle(value)} />
|
||||||
<button className="btn btn-sm btn-circle absolute right-4 top-4" onClick={close}>
|
|
||||||
<Icon.IoMdClose className="w-5 h-auto" />
|
|
||||||
</button>
|
|
||||||
<div className="w-full flex flex-col justify-start items-start space-y-3 pt-4">
|
|
||||||
<TextField placeholder={t("conversation.conversation-title") || ""} value={title} onChange={(value) => setTitle(value)} />
|
|
||||||
</div>
|
|
||||||
<div className="modal-action">
|
|
||||||
<button className="btn btn-outline" onClick={close}>
|
|
||||||
{t("common.close")}
|
|
||||||
</button>
|
|
||||||
<button className="btn" disabled={!allowSave} onClick={handleSaveEdit}>
|
|
||||||
{t("common.save")}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="w-full flex flex-row justify-end items-center mt-4 space-x-2">
|
||||||
|
<button className="btn btn-outline" onClick={close}>
|
||||||
|
{t("common.close")}
|
||||||
|
</button>
|
||||||
|
<button className="btn" disabled={!allowSave} onClick={handleSaveEdit}>
|
||||||
|
{t("common.save")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import Dialog from "./kit/Dialog";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import WeChatQRCodeView from "./WeChatQRCodeView";
|
import WeChatQRCodeView from "./WeChatQRCodeView";
|
||||||
import ClearDataButton from "./ClearDataButton";
|
import ClearDataButton from "./ClearDataButton";
|
||||||
@ -6,54 +7,47 @@ import LocaleSelector from "./LocaleSelector";
|
|||||||
import OpenAIApiConfigView from "./OpenAIApiConfigView";
|
import OpenAIApiConfigView from "./OpenAIApiConfigView";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
show: boolean;
|
|
||||||
close: () => void;
|
close: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SettingModal = (props: Props) => {
|
const SettingModal = (props: Props) => {
|
||||||
const { show, close } = props;
|
const { close } = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`modal modal-middle ${show && "modal-open"}`}>
|
<Dialog title={t("setting.self")} onClose={close}>
|
||||||
<div className="modal-box relative">
|
<div className="w-full flex flex-col justify-start items-start space-y-3 pt-4">
|
||||||
<h3 className="font-bold text-lg">{t("setting.self")}</h3>
|
<div className="w-full flex flex-row justify-start items-start flex-wrap">
|
||||||
<button className="btn btn-sm btn-circle absolute right-4 top-4" onClick={close}>
|
<a
|
||||||
<Icon.IoMdClose className="w-5 h-auto" />
|
href="https://discord.gg/z6kakemDjm"
|
||||||
</button>
|
className="w-auto px-4 py-2 rounded-full bg-indigo-600 text-white text-sm font-medium flex flex-row justify-center items-center mr-2 mb-2 hover:underline hover:shadow"
|
||||||
<div className="w-full flex flex-col justify-start items-start space-y-3 pt-4">
|
target="_blank"
|
||||||
<div className="w-full flex flex-row justify-start items-start flex-wrap">
|
>
|
||||||
<a
|
<Icon.BsDiscord className="w-4 h-auto mr-1" />
|
||||||
href="https://discord.gg/z6kakemDjm"
|
{t("social.join-discord-channel")}
|
||||||
className="w-auto px-4 py-2 rounded-full bg-indigo-600 text-white text-sm font-medium flex flex-row justify-center items-center mr-2 mb-2 hover:underline hover:shadow"
|
</a>
|
||||||
target="_blank"
|
<WeChatQRCodeView />
|
||||||
>
|
</div>
|
||||||
<Icon.BsDiscord className="w-4 h-auto mr-1" />
|
|
||||||
{t("social.join-discord-channel")}
|
<h3 className="pl-4 text-sm text-gray-500">{t("setting.basic.self")}</h3>
|
||||||
</a>
|
<div className="w-full border border-gray-200 p-4 rounded-lg">
|
||||||
<WeChatQRCodeView />
|
<div className="w-full flex flex-row justify-between items-center gap-2">
|
||||||
|
<span>{t("setting.basic.language")}</span>
|
||||||
|
<LocaleSelector />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h3 className="pl-4 text-sm text-gray-500">{t("setting.basic.self")}</h3>
|
<OpenAIApiConfigView />
|
||||||
<div className="w-full border border-gray-200 p-4 rounded-lg">
|
|
||||||
<div className="w-full flex flex-row justify-between items-center gap-2">
|
|
||||||
<span>{t("setting.basic.language")}</span>
|
|
||||||
<LocaleSelector />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<OpenAIApiConfigView />
|
<h3 className="pl-4 text-sm text-gray-500">{t("setting.data.self")}</h3>
|
||||||
|
<div className="w-full border border-red-200 p-4 rounded-lg">
|
||||||
<h3 className="pl-4 text-sm text-gray-500">{t("setting.data.self")}</h3>
|
<div className="w-full flex flex-row justify-between items-center gap-2">
|
||||||
<div className="w-full border border-red-200 p-4 rounded-lg">
|
<span>{t("setting.data.clear-all-data")}</span>
|
||||||
<div className="w-full flex flex-row justify-between items-center gap-2">
|
<ClearDataButton />
|
||||||
<span>{t("setting.data.clear-all-data")}</span>
|
|
||||||
<ClearDataButton />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Dialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
34
src/components/kit/Dialog.tsx
Normal file
34
src/components/kit/Dialog.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import * as DialogUI from "@radix-ui/react-dialog";
|
||||||
|
import React, { ReactNode } from "react";
|
||||||
|
import Icon from "../Icon";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title: string;
|
||||||
|
children: ReactNode;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Dialog = (props: Props) => {
|
||||||
|
const { children, title, onClose } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogUI.Root open={true}>
|
||||||
|
<DialogUI.Portal>
|
||||||
|
<DialogUI.Overlay className="fixed inset-0 bg-black bg-opacity-60 z-[9999]" />
|
||||||
|
<DialogUI.Content className="bg-white rounded-xl p-4 px-5 fixed top-[50%] left-[50%] max-h-[85vh] w-[90vw] max-w-[450px] translate-x-[-50%] translate-y-[-50%] z-[9999]">
|
||||||
|
<DialogUI.Title className="text-lg text-black font-medium mb-2">{title}</DialogUI.Title>
|
||||||
|
<DialogUI.Close
|
||||||
|
className="absolute top-3 right-3 outline-none w-8 h-8 p-1 bg-zinc-600 rounded-full text-white hover:opacity-80"
|
||||||
|
aria-label="Close"
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
<Icon.IoClose className="w-full h-auto" />
|
||||||
|
</DialogUI.Close>
|
||||||
|
<div className="w-full flex flex-col justify-start items-start">{children}</div>
|
||||||
|
</DialogUI.Content>
|
||||||
|
</DialogUI.Portal>
|
||||||
|
</DialogUI.Root>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Dialog;
|
Reference in New Issue
Block a user