diff --git a/client/packages/lowcoder-comps/package.json b/client/packages/lowcoder-comps/package.json index 081b36f1d..264e38233 100644 --- a/client/packages/lowcoder-comps/package.json +++ b/client/packages/lowcoder-comps/package.json @@ -1,6 +1,6 @@ { "name": "lowcoder-comps", - "version": "0.0.12", + "version": "0.0.13", "type": "module", "license": "MIT", "dependencies": { @@ -14,6 +14,7 @@ "@types/react": "17", "@types/react-dom": "17", "big.js": "^6.2.1", + "echarts-extension-gmap": "^1.6.0", "lowcoder-cli": "workspace:^", "lowcoder-sdk": "workspace:^", "mermaid": "^10.2.4", diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartComp.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/chartComp.tsx index 5f94533a8..e0dcf03cc 100644 --- a/client/packages/lowcoder-comps/src/comps/chartComp/chartComp.tsx +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartComp.tsx @@ -26,6 +26,7 @@ import { withViewFn, ThemeContext, chartColorPalette, + loadScript, } from "lowcoder-sdk"; import { getEchartsLocale, trans } from "i18n/comps"; import { ItemColorComp } from "comps/chartComp/chartConfigs/lineChartConfig"; @@ -33,7 +34,9 @@ import { echartsConfigOmitChildren, getEchartsConfig, getSelectedPoints, + loadGoogleMapsScript, } from "comps/chartComp/chartUtils"; +import 'echarts-extension-gmap'; import log from "loglevel"; let ChartTmpComp = (function () { @@ -45,6 +48,7 @@ let ChartTmpComp = (function () { ChartTmpComp = withViewFn(ChartTmpComp, (comp) => { const echartsCompRef = useRef(); const [chartSize, setChartSize] = useState(); + const [mapScriptLoaded, setMapScriptLoaded] = useState(false); const firstResize = useRef(true); const theme = useContext(ThemeContext); const defaultChartTheme = { @@ -87,6 +91,34 @@ ChartTmpComp = withViewFn(ChartTmpComp, (comp) => { ); }, [chartSize, ...Object.values(echartsConfigChildren)]); + const isMapScriptLoaded = useMemo(() => { + return mapScriptLoaded || window?.google; + }, [mapScriptLoaded]) + + const loadGoogleMapsData = () => { + const echartsCompInstance = echartsCompRef?.current?.getEchartsInstance(); + if (!echartsCompInstance) { + return _.noop; + } + echartsCompInstance.getModel().getComponent("gmap").getGoogleMap(); + } + + const apiKey = comp.children.mapApiKey.getView(); + const mode = comp.children.mode.getView(); + useEffect(() => { + if(mode === 'map') { + const gMapScript = loadGoogleMapsScript(''); + if(isMapScriptLoaded) { + loadGoogleMapsData(); + return; + } + gMapScript.addEventListener('load', function () { + setMapScriptLoaded(true); + loadGoogleMapsData(); + }); + } + }, [mode, apiKey, option]) + return ( { @@ -101,15 +133,17 @@ ChartTmpComp = withViewFn(ChartTmpComp, (comp) => { } }} > - (echartsCompRef.current = e)} - style={{ height: "100%" }} - notMerge - lazyUpdate - opts={{ locale: getEchartsLocale() }} - option={option} - theme={themeConfig} - /> + {(mode !== 'map' || (mode === 'map' && isMapScriptLoaded)) && ( + (echartsCompRef.current = e)} + style={{ height: "100%" }} + notMerge + lazyUpdate + opts={{ locale: getEchartsLocale() }} + option={option} + theme={mode !== 'map' ? themeConfig : undefined} + /> + )} ); }); diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/chartUrls.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/chartUrls.tsx index a92e5c06a..ef8ada4b0 100644 --- a/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/chartUrls.tsx +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/chartUrls.tsx @@ -4,3 +4,6 @@ const echartsUrlLocale = language === "zh" ? "zh" : "en"; export const optionUrl = `https://echarts.apache.org/${echartsUrlLocale}/option.html`; export const examplesUrl = `https://echarts.apache.org/examples/${echartsUrlLocale}/index.html`; export const xAxisTypeUrl = `${optionUrl}#xAxis.type`; +export const googleMapsApiUrl = `https://maps.googleapis.com/maps/api/js`; +export const mapOptionUrl = `https://github.com/plainheart/echarts-extension-gmap`; +export const mapExamplesUrl = `https://codepen.io/plainheart/pen/VweLGbR`; \ No newline at end of file diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartConstants.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/chartConstants.tsx index 52db1c5ab..52e53b2b6 100644 --- a/client/packages/lowcoder-comps/src/comps/chartComp/chartConstants.tsx +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartConstants.tsx @@ -1,5 +1,5 @@ import { jsonControl, JSONObject, stateComp, toJSONObjectArray, toObject } from "lowcoder-sdk"; -import { StringControl } from "lowcoder-sdk"; +import { withDefault, BooleanControl, StringControl, NumberControl, JSONObjectControl } from "lowcoder-sdk"; import { dropdownControl } from "lowcoder-sdk"; import { eventHandlerControl } from "lowcoder-sdk"; import { valueComp, withType } from "lowcoder-sdk"; @@ -15,7 +15,6 @@ import { ScatterChartConfig } from "./chartConfigs/scatterChartConfig"; import { SeriesListComp } from "./seriesComp"; import { EChartsOption } from "echarts"; import { i18nObjs, trans } from "i18n/comps"; -import { JSONValue } from "lowcoder"; export const ChartTypeOptions = [ { @@ -45,6 +44,10 @@ const chartModeOptions = [ label: "ECharts JSON", value: "json", }, + { + label: "Map", + value: "map", + }, ] as const; export const EventOptions = [ @@ -221,6 +224,14 @@ export const chartUiModeChildren = { chartConfig: ChartOptionComp, }; +const chartMapModeChildren = { + mapApiKey: withDefault(StringControl, ''), + mapZoomLevel: withDefault(NumberControl, 3), + mapCenterLng: withDefault(NumberControl, 15.932644), + mapCenterLat: withDefault(NumberControl, 50.942063), + mapOptions: jsonControl(toObject, i18nObjs.defaultMapJsonOption), +} + export const chartChildrenMap = { mode: dropdownControl(chartModeOptions, "ui"), echartsOption: jsonControl(toObject, i18nObjs.defaultEchartsJsonOption), @@ -236,6 +247,7 @@ export const chartChildrenMap = { }> >([]), ...chartUiModeChildren, + ...chartMapModeChildren, }; const chartUiChildrenMap = uiChildren(chartChildrenMap); diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartPropertyView.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/chartPropertyView.tsx index 113c71c13..0e114f1d3 100644 --- a/client/packages/lowcoder-comps/src/comps/chartComp/chartPropertyView.tsx +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartPropertyView.tsx @@ -9,9 +9,10 @@ import { RedButton, Section, sectionNames, + controlItem, } from "lowcoder-sdk"; import { trans } from "i18n/comps"; -import { examplesUrl, optionUrl } from "./chartConfigs/chartUrls"; +import { examplesUrl, mapExamplesUrl, mapOptionUrl, optionUrl } from "./chartConfigs/chartUrls"; export function chartPropertyView( children: ChartCompChildrenType, @@ -147,6 +148,58 @@ export function chartPropertyView( ); + const mapModePropertyView = ( + <> +
+ {children.mapApiKey.propertyView({ + label: "API Key" + })} + {children.mapZoomLevel.propertyView({ + label: "Zoom Level" + })} + {controlItem({}, ( + + {'Center Position'} + + ))} + {children.mapCenterLng.propertyView({ + label: "Longitude" + })} + {children.mapCenterLat.propertyView({ + label: "Latitude" + })} +
+
+ {children.mapOptions.propertyView({ + label: trans("chart.echartsOptionLabel"), + styleName: "higher", + tooltip: ( +
+ + {trans("chart.echartsMapOptionTooltip")} + +
+ + {trans("chart.echartsMapOptionExamples")} + +
+ ), + })} +
+
{hiddenPropertyView(children)}
+ + ); + + const getChatConfigByMode = (mode: string) => { + switch(mode) { + case "ui": + return uiModePropertyView; + case "json": + return jsonModePropertyView; + case "map": + return mapModePropertyView; + } + } return ( <>
@@ -155,7 +208,7 @@ export function chartPropertyView( radioButton: true, })}
- {children.mode.getView() === "ui" ? uiModePropertyView : jsonModePropertyView} + {getChatConfigByMode(children.mode.getView())} ); } diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartUtils.ts b/client/packages/lowcoder-comps/src/comps/chartComp/chartUtils.ts index 280992714..14db6b7b8 100644 --- a/client/packages/lowcoder-comps/src/comps/chartComp/chartUtils.ts +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartUtils.ts @@ -6,11 +6,12 @@ import { noDataPieChartConfig, } from "comps/chartComp/chartConstants"; import { getPieRadiusAndCenter } from "comps/chartComp/chartConfigs/pieChartConfig"; -import { EChartsOption } from "echarts"; +import { EChartsOptionWithMap } from "./reactEcharts/types"; import _ from "lodash"; -import { chartColorPalette, isNumeric, JSONObject } from "lowcoder-sdk"; +import { chartColorPalette, isNumeric, JSONObject, loadScript } from "lowcoder-sdk"; import { calcXYConfig } from "comps/chartComp/chartConfigs/cartesianAxisConfig"; import Big from "big.js"; +import { googleMapsApiUrl } from "./chartConfigs/chartUrls"; export function transformData( originData: JSONObject[], @@ -122,10 +123,30 @@ export function getSeriesConfig(props: EchartsConfigProps) { } // https://echarts.apache.org/en/option.html -export function getEchartsConfig(props: EchartsConfigProps, chartSize?: ChartSize): EChartsOption { +export function getEchartsConfig(props: EchartsConfigProps, chartSize?: ChartSize): EChartsOptionWithMap { if (props.mode === "json") { return props.echartsOption ? props.echartsOption : {}; } + if(props.mode === "map") { + const { + mapZoomLevel, + mapCenterLat, + mapCenterLng, + mapOptions, + } = props; + + const echartsOption = mapOptions ? mapOptions : {}; + return { + gmap: { + center: [mapCenterLng, mapCenterLat], + zoom: mapZoomLevel, + renderOnMoving: true, + // echartsLayerZIndex: 2019, + roam: true + }, + ...echartsOption, + } + } // axisChart const axisChart = isAxisChart(props.chartConfig.type); const gridPos = { @@ -134,7 +155,7 @@ export function getEchartsConfig(props: EchartsConfigProps, chartSize?: ChartSiz top: 50, bottom: 35, }; - let config: EChartsOption = { + let config: EChartsOptionWithMap = { title: { text: props.title, left: "center" }, tooltip: { confine: true, @@ -238,3 +259,21 @@ export function getSelectedPoints(param: any, option: any) { } return []; } + +export function loadGoogleMapsScript(apiKey?: string) { + const mapsUrl = `${googleMapsApiUrl}?key=${apiKey}`; + const scripts = document.getElementsByTagName('script'); + const scriptIndex = _.findIndex(scripts, (script) => script.src === mapsUrl); + if(scriptIndex > -1) { + return scripts[scriptIndex]; + } + + const script = document.createElement("script"); + script.type = "text/javascript"; + script.src = mapsUrl; + script.async = true; + script.defer = true; + window.document.body.appendChild(script); + + return script; +} diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/reactEcharts/core.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/reactEcharts/core.tsx index dd303e5fb..c90bd5b3d 100644 --- a/client/packages/lowcoder-comps/src/comps/chartComp/reactEcharts/core.tsx +++ b/client/packages/lowcoder-comps/src/comps/chartComp/reactEcharts/core.tsx @@ -58,7 +58,8 @@ export default class EChartsReactCore extends PureComponent { if ( !isEqual(prevProps.theme, this.props.theme) || !isEqual(prevProps.opts, this.props.opts) || - !isEqual(prevProps.onEvents, this.props.onEvents) + !isEqual(prevProps.onEvents, this.props.onEvents) || + this.props.option.gmap ) { this.dispose(); diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/reactEcharts/index.ts b/client/packages/lowcoder-comps/src/comps/chartComp/reactEcharts/index.ts index 80fc9ae9e..6be260645 100644 --- a/client/packages/lowcoder-comps/src/comps/chartComp/reactEcharts/index.ts +++ b/client/packages/lowcoder-comps/src/comps/chartComp/reactEcharts/index.ts @@ -1,5 +1,5 @@ import * as echarts from "echarts"; -import { EChartsReactProps, EChartsOption, EChartsInstance } from "./types"; +import { EChartsReactProps, EChartsInstance, EChartsOptionWithMap } from "./types"; import EChartsReactCore from "./core"; /** @@ -7,7 +7,7 @@ import EChartsReactCore from "./core"; * add exception-catch for setOption * if query isn't successfully loaded, chart will fail to load and can't reload */ -export type { EChartsReactProps, EChartsOption, EChartsInstance }; +export type { EChartsReactProps, EChartsOptionWithMap, EChartsInstance }; // export the Component the echarts Object. export default class EChartsReact extends EChartsReactCore { diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/reactEcharts/types.ts b/client/packages/lowcoder-comps/src/comps/chartComp/reactEcharts/types.ts index c9f1ec973..b686408a4 100644 --- a/client/packages/lowcoder-comps/src/comps/chartComp/reactEcharts/types.ts +++ b/client/packages/lowcoder-comps/src/comps/chartComp/reactEcharts/types.ts @@ -1,6 +1,8 @@ import { CSSProperties } from "react"; +import { EChartsOption } from "echarts"; +import { GoogleMapComponentOption } from "echarts-extension-gmap"; -export type EChartsOption = any; +export type EChartsOptionWithMap = EChartsOption & GoogleMapComponentOption; export type EChartsInstance = any; @@ -28,7 +30,7 @@ export type EChartsReactProps = { /** * echarts option */ - readonly option: EChartsOption; + readonly option: EChartsOptionWithMap; /** * echarts theme config, can be: * 1. theme name string diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts b/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts index a9cdb829d..e64f7694d 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts @@ -57,6 +57,8 @@ export const en = { echartsOptionLabel: "Option", echartsOptionTooltip: "ECharts option", echartsOptionExamples: "ECharts examples", + echartsMapOptionTooltip: "ECharts Map Option", + echartsMapOptionExamples: "ECharts Map Examples", selectDesc: "Triggered when the user selects part of the data in the chart", unselectDesc: "Triggered when the user unselects part of the data in the chart", selectedPointsDesc: "Selected points", diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx b/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx index c96adaba1..6f82ef845 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx @@ -1,6 +1,82 @@ import { I18nObjects } from "./types"; import { chartColorPalette } from "lowcoder-sdk"; + +const defaultMapData = { + tooltip: { + trigger: "item" + }, + animation: true, + series: [ + { + name: 'Population', + type: 'scatter', + coordinateSystem: 'gmap', + itemStyle: { + color: "#00c1de" + }, + data: [ + { "name":"Azerbaijan","value":[47.395,40.43,8352021] }, + { "name":"Albania","value":[20.068,41.143,3153731] }, + { "name":"Armenia","value":[44.563,40.534,3017661] }, + { "name":"Bosnia and Herzegovina","value":[17.786,44.169,3915238] }, + { "name":"Bulgaria","value":[25.231,42.761,7744591] }, + { "name":"Cyprus","value":[33.219,35.043,836321] }, + { "name":"Denmark","value":[9.264,56.058,5416945] }, + { "name":"Ireland","value":[-8.152,53.177,4143294] }, + { "name":"Estonia","value":[25.793,58.674,1344312] }, + { "name":"Austria","value":[14.912,47.683,8291979] }, + { "name":"Czech Republic","value":[15.338,49.743,10191762] }, + { "name":"Finland","value":[26.272,64.504,5246004] }, + { "name":"France","value":[2.55,46.565,60990544] }, + { "name":"Georgia","value":[43.518,42.176,4473409] }, + { "name":"Germany","value":[9.851,51.11,82652369] }, + { "name":"Greece","value":[21.766,39.666,11099737] }, + { "name":"Croatia","value":[16.693,45.723,455149] }, + { "name":"Hungary","value":[19.134,47.07,10086387] }, + { "name":"Iceland","value":[-18.48,64.764,295732] }, + { "name":"Israel","value":[34.851,31.026,6692037] }, + { "name":"Italy","value":[12.8,42.7,5864636] }, + { "name":"Latvia","value":[25.641,56.858,2301793] }, + { "name":"Belarus","value":[28.047,53.54,9795287] }, + { "name":"Lithuania","value":[23.897,55.336,3425077] }, + { "name":"Slovakia","value":[19.491,48.707,5386995] }, + { "name":"Liechtenstein","value":[9.555,47.153,34598] }, + { "name":"The former Yugoslav Republic of Macedonia","value":[21.698,41.6,2033655] }, + { "name":"Malta","value":[14.442,35.89,402617] }, + { "name":"Belgium","value":[4.664,50.643,10398049] }, + { "name":"Faroe Islands","value":[-6.864,62.05,48205] }, + { "name":"Andorra","value":[1.576,42.549,73483] }, + { "name":"Luxembourg","value":[6.088,49.771,456613] }, + { "name":"Monaco","value":[7.412,43.75,325] }, + { "name":"Montenegro","value":[19.254,42.792,607969] }, + { "name":"Netherlands","value":[5.389,52.077,1632769] }, + { "name":"Norway","value":[8.74,61.152,4638836] }, + { "name":"Poland","value":[19.401,52.125,38195558] }, + { "name":"Portugal","value":[-8.058,40.309,10528226] }, + { "name":"Romania","value":[24.969,45.844,21627557] }, + { "name":"Republic of Moldova","value":[28.599,47.193,3876661] }, + { "name":"Slovenia","value":[14.827,46.124,1999425] }, + { "name":"Spain","value":[-3.649,40.227,43397491] }, + { "name":"Sweden","value":[15.27,62.011,9038049] }, + { "name":"Switzerland","value":[7.908,46.861,7424389] }, + { "name":"Turkey","value":[35.179,39.061,72969723] }, + { "name":"United Kingdom","value":[-1.6,53,60244834] }, + { "name":"Ukraine","value":[31.388,49.016,46917544] }, + { "name":"San Marino","value":[12.46,43.942,30214] }, + { "name":"Serbia","value":[20.806,44.032,9863026] }, + { "name":"Holy See (Vatican City)","value":[12.451,41.904,783] }, + { "name":"Russia","value":[96.689,61.988,143953092]} + ], + encode: { + value: 2, + lng: 0, + lat: 1 + } + } + ] +} + export const enObj: I18nObjects = { defaultDataSource: [ { @@ -117,4 +193,6 @@ export const enObj: I18nObjects = { }, ], }, + + defaultMapJsonOption: defaultMapData, }; diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx b/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx index 821f631c6..92f3cc4b6 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx @@ -4,6 +4,7 @@ import { XAXisComponentOption } from "echarts"; export type I18nObjects = { defaultDataSource: JSONObject[]; defaultEchartsJsonOption: Record; + defaultMapJsonOption: Record; timeXAxisLabel?: XAXisComponentOption["axisLabel"]; imageEditorLocale?: Record; }; diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/zh.ts b/client/packages/lowcoder-comps/src/i18n/comps/locales/zh.ts index cf27ca875..281d6e65e 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/zh.ts +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/zh.ts @@ -56,6 +56,8 @@ export const zh = { echartsOptionLabel: "选项", echartsOptionTooltip: "ECharts选项", echartsOptionExamples: "ECharts示例", + echartsMapOptionTooltip: "ECharts地图选项", + echartsMapOptionExamples: "ECharts地图示例", selectDesc: "当用户选择图表中的部分数据时触发", unselectDesc: "当用户取消选择图表中的部分数据时触发", selectedPointsDesc: "已选中的数据点", diff --git a/client/packages/lowcoder/src/components/ResCreatePanel.tsx b/client/packages/lowcoder/src/components/ResCreatePanel.tsx index 0b598ab7b..c5c85968a 100644 --- a/client/packages/lowcoder/src/components/ResCreatePanel.tsx +++ b/client/packages/lowcoder/src/components/ResCreatePanel.tsx @@ -161,6 +161,13 @@ const ResButton = (props: { dataSourceId: QUICK_REST_API_ID, }, }, + streamApi: { + label: trans("query.quickStreamAPI"), + type: BottomResTypeEnum.Query, + extra: { + compType: "streamApi", + }, + }, graphql: { label: trans("query.quickGraphql"), type: BottomResTypeEnum.Query, @@ -318,6 +325,7 @@ export function ResCreatePanel(props: ResCreateModalProps) {
+ {placement === "editor" && ( diff --git a/client/packages/lowcoder/src/comps/queries/httpQuery/httpQueryConstants.tsx b/client/packages/lowcoder/src/comps/queries/httpQuery/httpQueryConstants.tsx index f4b39272f..a0494477f 100644 --- a/client/packages/lowcoder/src/comps/queries/httpQuery/httpQueryConstants.tsx +++ b/client/packages/lowcoder/src/comps/queries/httpQuery/httpQueryConstants.tsx @@ -5,6 +5,7 @@ import { HttpQuery } from "./httpQuery"; import styled from "styled-components"; import { QueryConfigItemWrapper, QueryConfigLabel, QueryConfigWrapper } from "components/query"; import { GraphqlQuery } from "./graphqlQuery"; +import { StreamQuery } from "./streamQuery"; const UrlInput = styled.div<{ hasAddonBefore: boolean }>` display: flex; @@ -33,7 +34,7 @@ const UrlInputAddonBefore = styled.div` `; export const HttpPathPropertyView = (props: { - comp: InstanceType; + comp: InstanceType; datasourceId: string; urlPlaceholder?: string; }) => { diff --git a/client/packages/lowcoder/src/comps/queries/httpQuery/streamQuery.tsx b/client/packages/lowcoder/src/comps/queries/httpQuery/streamQuery.tsx new file mode 100644 index 000000000..b643cc281 --- /dev/null +++ b/client/packages/lowcoder/src/comps/queries/httpQuery/streamQuery.tsx @@ -0,0 +1,129 @@ +import { simpleMultiComp } from "comps/generators/multi"; +import { ParamsStringControl } from "../../controls/paramsControl"; +import { + HttpPathPropertyView, +} from "./httpQueryConstants"; +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"; + +const socketConnection = async (socket: WebSocket, timeout = 10000) => { + const isOpened = () => (socket.readyState === WebSocket.OPEN) + + if (socket.readyState !== WebSocket.CONNECTING) { + return isOpened() + } + else { + const intrasleep = 100 + const ttl = timeout / intrasleep // time to loop + let loop = 0 + while (socket.readyState === WebSocket.CONNECTING && loop < ttl) { + await new Promise(resolve => setTimeout(resolve, intrasleep)) + loop++ + } + return isOpened() + } +} + +const createSuccessResponse = ( + data: JSONValue, + runTime?: number, +): QueryResult => { + return { + data, + runTime, + success: true, + code: QUERY_EXECUTION_OK, + } +} + +const createErrorResponse = ( + message: string, +): QueryResult => { + return { + message, + data: "", + success: false, + code: QUERY_EXECUTION_ERROR, + } +} + +const childrenMap = { + path: ParamsStringControl, + destroySocketConnection: FunctionControl, +}; + +const StreamTmpQuery = simpleMultiComp(childrenMap); + +export class StreamQuery extends StreamTmpQuery { + private socket: WebSocket | undefined; + + override getView() { + return async ( + p: { + args?: Record, + callback?: (result: QueryResult) => void + } + ): Promise => { + const children = this.children; + + try { + const timer = performance.now(); + const socketUrl = children.path.children.text.getView(); + + this.socket = new WebSocket(socketUrl); + this.socket.onopen = () => { + console.log("[WebSocket] Connection established"); + } + + this.socket.onmessage = (event) => { + console.log(`[WebSocket] Data received from server`); + if(typeof JSON.parse(event.data) === 'object') { + const result = createSuccessResponse(JSON.parse(event.data)) + p?.callback?.(result); + } + } + + this.socket.onclose = () => { + console.log(`[WebSocket] Connection closed`); + } + + this.socket.onerror = function(error) { + throw new Error(error as any) + } + + const isConnectionOpen = await socketConnection(this.socket); + if(!isConnectionOpen) { + return createErrorResponse("Socket connection failed") + } + + return createSuccessResponse("", Number((performance.now() - timer).toFixed())) + } catch (e) { + return createErrorResponse((e as any).message || "") + } + }; + } + + propertyView(props: { datasourceId: string }) { + return ; + } + + destroy() { + this.socket?.close(); + } +} + +const PropertyView = (props: { comp: InstanceType; datasourceId: string }) => { + const { comp } = props; + + return ( + <> + + + ); +}; diff --git a/client/packages/lowcoder/src/comps/queries/queryComp.tsx b/client/packages/lowcoder/src/comps/queries/queryComp.tsx index 40c278f74..b3a483e2a 100644 --- a/client/packages/lowcoder/src/comps/queries/queryComp.tsx +++ b/client/packages/lowcoder/src/comps/queries/queryComp.tsx @@ -69,6 +69,7 @@ import { millisecondsControl } from "../controls/millisecondControl"; import { paramsMillisecondsControl } from "../controls/paramsControl"; import { NameConfig, withExposingConfigs } from "../generators/withExposing"; import { HttpQuery } from "./httpQuery/httpQuery"; +import { StreamQuery } from "./httpQuery/streamQuery"; import { QueryConfirmationModal } from "./queryComp/queryConfirmationModal"; import { QueryNotificationControl } from "./queryComp/queryNotificationControl"; import { QueryPropertyView } from "./queryComp/queryPropertyView"; @@ -419,6 +420,7 @@ QueryCompTmp = class extends QueryCompTmp { applicationPath: parentApplicationPath, args: action.args, timeout: this.children.timeout, + callback: (result) => this.processResult(result, action, startTime) }); }, getTriggerType(this) === "manual") .then( @@ -517,6 +519,7 @@ QueryCompTmp = class extends QueryCompTmp implements BottomResComp { switch (type) { case "js": case "restApi": + case "streamApi": case "mongodb": case "redis": case "es": @@ -708,6 +711,9 @@ class QueryListComp extends QueryListTmpComp implements BottomResListComp { ], }) ); + if(toDelQuery.children.compType.getView() === 'streamApi') { + (toDelQuery.children.comp as StreamQuery)?.destroy(); + } messageInstance.success(trans("query.deleteSuccessMessage", { undoKey })); } diff --git a/client/packages/lowcoder/src/comps/queries/queryComp/queryPropertyView.tsx b/client/packages/lowcoder/src/comps/queries/queryComp/queryPropertyView.tsx index fbf5264c2..516c1aa7d 100644 --- a/client/packages/lowcoder/src/comps/queries/queryComp/queryPropertyView.tsx +++ b/client/packages/lowcoder/src/comps/queries/queryComp/queryPropertyView.tsx @@ -307,7 +307,7 @@ export const QueryGeneralPropertyView = (props: { [ { label: - children.compType.getView() === "js" + (children.compType.getView() === "js" || children.compType.getView() === "streamApi") ? trans("query.triggerTypePageLoad") : trans("query.triggerTypeAuto"), value: "automatic", @@ -363,6 +363,7 @@ function useDatasourceStatus(datasourceId: string, datasourceType: ResourceType) return useMemo(() => { if ( datasourceType === "js" || + datasourceType === "streamApi" || datasourceType === "libraryQuery" || datasourceId === QUICK_REST_API_ID || datasourceId === QUICK_GRAPHQL_ID || diff --git a/client/packages/lowcoder/src/comps/queries/resourceDropdown.tsx b/client/packages/lowcoder/src/comps/queries/resourceDropdown.tsx index 3adab66fa..d4b60a063 100644 --- a/client/packages/lowcoder/src/comps/queries/resourceDropdown.tsx +++ b/client/packages/lowcoder/src/comps/queries/resourceDropdown.tsx @@ -91,6 +91,11 @@ const QuickRestAPIValue: ResourceOptionValue = { type: "restApi", }; +const QuickStreamAPIValue: ResourceOptionValue = { + id: "", + type: "streamApi", +}; + const QuickGraphqlValue: ResourceOptionValue = { id: QUICK_GRAPHQL_ID, type: "graphql", @@ -254,6 +259,17 @@ export const ResourceDropdown = (props: ResourceDropdownProps) => { + + + {getBottomResIcon("restApi")} + {trans("query.quickStreamAPI")} + + +