From 2907b39dfc4e256640eee00a04e52252543bd197 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Sat, 16 Sep 2023 16:23:37 +0500 Subject: [PATCH] feat: allow user to disconnect socket connection and send message by socket --- .../comps/queries/httpQuery/streamQuery.tsx | 56 +++++++++++- .../queries/queryComp/queryPropertyView.tsx | 12 +++ .../src/pages/editor/bottom/BottomTabs.tsx | 91 ++++++++++++++++--- 3 files changed, 140 insertions(+), 19 deletions(-) diff --git a/client/packages/lowcoder/src/comps/queries/httpQuery/streamQuery.tsx b/client/packages/lowcoder/src/comps/queries/httpQuery/streamQuery.tsx index b643cc281..32d952f03 100644 --- a/client/packages/lowcoder/src/comps/queries/httpQuery/streamQuery.tsx +++ b/client/packages/lowcoder/src/comps/queries/httpQuery/streamQuery.tsx @@ -1,5 +1,5 @@ import { simpleMultiComp } from "comps/generators/multi"; -import { ParamsStringControl } from "../../controls/paramsControl"; +import { ParamsStringControl } from "comps/controls/paramsControl"; import { HttpPathPropertyView, } from "./httpQueryConstants"; @@ -7,6 +7,9 @@ import { QueryResult } from "../queryComp"; import { QUERY_EXECUTION_ERROR, QUERY_EXECUTION_OK } from "constants/queryConstants"; import { FunctionControl } from "comps/controls/codeControl"; import { JSONValue } from "util/jsonTypes"; +import { withMethodExposing } from "comps/generators/withMethodExposing"; +import { stateComp } from "comps/generators"; +import { multiChangeAction } from "lowcoder-core"; const socketConnection = async (socket: WebSocket, timeout = 10000) => { const isOpened = () => (socket.readyState === WebSocket.OPEN) @@ -52,9 +55,29 @@ const createErrorResponse = ( const childrenMap = { path: ParamsStringControl, destroySocketConnection: FunctionControl, + isSocketConnected: stateComp(false), }; -const StreamTmpQuery = simpleMultiComp(childrenMap); +let StreamTmpQuery = simpleMultiComp(childrenMap); + +StreamTmpQuery = withMethodExposing(StreamTmpQuery, [ + { + method: { + name: "broadcast", + params: [{ name: "args", type: "JSON" }], + }, + execute: (comp, params) => { + return new Promise((resolve, reject) => { + const tmpComp = (comp as StreamQuery); + if(!tmpComp.getSocket()) { + return reject('Socket message send failed') + } + tmpComp.broadcast(params); + resolve({}); + }) + }, + }, +]) export class StreamQuery extends StreamTmpQuery { private socket: WebSocket | undefined; @@ -89,17 +112,29 @@ export class StreamQuery extends StreamTmpQuery { console.log(`[WebSocket] Connection closed`); } - this.socket.onerror = function(error) { + this.socket.onerror = (error) => { + this.destroy() throw new Error(error as any) } const isConnectionOpen = await socketConnection(this.socket); + if(!isConnectionOpen) { + this.destroy(); return createErrorResponse("Socket connection failed") } - return createSuccessResponse("", Number((performance.now() - timer).toFixed())) + this.dispatch( + multiChangeAction({ + isSocketConnected: this.children.isSocketConnected.changeValueAction(true), + }) + ); + return createSuccessResponse( + "Socket connection successfull", + Number((performance.now() - timer).toFixed()) + ) } catch (e) { + this.destroy(); return createErrorResponse((e as any).message || "") } }; @@ -111,6 +146,19 @@ export class StreamQuery extends StreamTmpQuery { destroy() { this.socket?.close(); + this.dispatch( + multiChangeAction({ + isSocketConnected: this.children.isSocketConnected.changeValueAction(false), + }) + ); + } + + broadcast(data: any) { + this.socket?.send(JSON.stringify(data)); + } + + getSocket() { + return this.socket; } } diff --git a/client/packages/lowcoder/src/comps/queries/queryComp/queryPropertyView.tsx b/client/packages/lowcoder/src/comps/queries/queryComp/queryPropertyView.tsx index 516c1aa7d..a17857348 100644 --- a/client/packages/lowcoder/src/comps/queries/queryComp/queryPropertyView.tsx +++ b/client/packages/lowcoder/src/comps/queries/queryComp/queryPropertyView.tsx @@ -30,6 +30,7 @@ import { EditorContext } from "../../editorState"; import { QueryComp } from "../queryComp"; import { ResourceDropdown } from "../resourceDropdown"; import { NOT_SUPPORT_GUI_SQL_QUERY, SQLQuery } from "../sqlQuery/SQLQuery"; +import { StreamQuery } from "../httpQuery/streamQuery"; export function QueryPropertyView(props: { comp: InstanceType }) { const { comp } = props; @@ -45,6 +46,7 @@ export function QueryPropertyView(props: { comp: InstanceType .datasourceConfig; const datasourceStatus = useDatasourceStatus(datasourceId, datasourceType); + const isStreamQuery = children.compType.getView() === 'streamApi'; return ( btnLoading={children.isFetching.getView()} status={datasourceStatus} message={datasourceStatus === "error" ? trans("query.dataSourceStatusError") : undefined} + isStreamQuery={isStreamQuery} + isSocketConnected={ + isStreamQuery + ? (children.comp as StreamQuery).children.isSocketConnected.getView() + : false + } + disconnectSocket={() => { + const streamQueryComp = comp.children.comp as StreamQuery; + streamQueryComp?.destroy(); + }} /> ); } diff --git a/client/packages/lowcoder/src/pages/editor/bottom/BottomTabs.tsx b/client/packages/lowcoder/src/pages/editor/bottom/BottomTabs.tsx index 3e0b966df..b99f78201 100644 --- a/client/packages/lowcoder/src/pages/editor/bottom/BottomTabs.tsx +++ b/client/packages/lowcoder/src/pages/editor/bottom/BottomTabs.tsx @@ -108,7 +108,7 @@ const TabContainer = styled.div` } } `; -const Button = styled(TacoButton)` +const RunButton = styled(TacoButton)` padding: 0 11px; flex: 0 0 64px; height: 24px; @@ -130,6 +130,41 @@ const Button = styled(TacoButton)` content: ""; } `; +const DisconnectButton = styled(TacoButton)` + padding: 0 11px; + flex: 0 0 64px; + height: 24px; + border: none; + color: white; + border-color: #079968; + background-color: #079968; + + :hover, :focus, :disabled, :disabled:hover { + padding: 0 11px; + border: none; + box-shadow: none; + } + + :disabled, + :disabled:hover { + background-color: #bbdecd !important; + border-color: #bbdecd; + } + + :hover { + background-color: #07714e; + border-color: #07714e; + } + + :focus { + background-color: #07714e; + border-color: #07714e; + } + + :after { + content: ""; + } +`; const ButtonLabel = styled.span` font-weight: 500; font-size: 13px; @@ -137,7 +172,7 @@ const ButtonLabel = styled.span` text-align: center; line-height: 24px; `; -const Icon = styled(UnfoldWhiteIcon)` +const RunIcon = styled(UnfoldWhiteIcon)` transform: rotate(-90deg); display: inline-block; padding-right: 2px; @@ -165,6 +200,9 @@ export function BottomTabs(props: { status?: "" | "error"; message?: string; runButtonText?: string; + isStreamQuery?: boolean; + isSocketConnected?: boolean; + disconnectSocket?: () => void; }) { const { type, @@ -175,6 +213,9 @@ export function BottomTabs(props: { status, message, runButtonText = trans("bottomPanel.run"), + isStreamQuery = false, + isSocketConnected = false, + disconnectSocket, } = props; const [key, setKey] = useState>("general"); const [error, setError] = useState(undefined); @@ -186,6 +227,31 @@ export function BottomTabs(props: { useEffect(() => setKey("general"), [editorState.selectedBottomResName]); + const RunButtonWrapper = () => ( + + + {runButtonText} + + ) + + const DisconnectButtonWrapper = () => ( + + + {'Disconnect'} + + + ) + return ( <> @@ -220,16 +286,11 @@ export function BottomTabs(props: { hasError={!!error} /> - {onRunBtnClick && ( - + {!isSocketConnected && onRunBtnClick && ( + + )} + {isSocketConnected && onRunBtnClick && disconnectSocket && ( + )} @@ -246,9 +307,9 @@ export function BottomTabs(props: { export const EmptyTab = ( - + );