}
>
);
-}
+}
\ No newline at end of file
diff --git a/client/packages/lowcoder/src/pages/datasource/datasourceEditPage.tsx b/client/packages/lowcoder/src/pages/datasource/datasourceEditPage.tsx
index 7e033cb7a5..9f8de01aa2 100644
--- a/client/packages/lowcoder/src/pages/datasource/datasourceEditPage.tsx
+++ b/client/packages/lowcoder/src/pages/datasource/datasourceEditPage.tsx
@@ -1,20 +1,25 @@
import styled from "styled-components";
import history from "../../util/history";
import { default as Button } from "antd/es/button";
-import { useCallback, useMemo, useState } from "react";
+import { Spin } from "antd";
+import { useCallback, useEffect, useMemo, useState } from "react";
import { CopyTextButton, DocIcon, PackUpIcon, TacoButton } from "lowcoder-design";
import { useDatasourceForm } from "./form/useDatasourceForm";
import { useParams } from "react-router-dom";
import { DATASOURCE_URL } from "../../constants/routesURL";
import { useSelector } from "react-redux";
-import { getDataSource, getDataSourceTypes } from "../../redux/selectors/datasourceSelectors";
+import { getDataSourceTypes } from "../../redux/selectors/datasourceSelectors";
import { trans } from "i18n";
import { DatasourceType } from "@lowcoder-ee/constants/queryConstants";
import { getDatasourceTutorial } from "@lowcoder-ee/util/tutorialUtils";
import { getDataSourceFormManifest } from "./getDataSourceFormManifest";
import DataSourceIcon from "components/DataSourceIcon";
import { Helmet } from "react-helmet";
-
+import { DatasourceApi } from "@lowcoder-ee/api/datasourceApi";
+import { DatasourceInfo } from "@lowcoder-ee/api/datasourceApi";
+import { GenericApiResponse } from "../../api/apiResponses";
+import { Datasource } from "@lowcoder-ee/constants/datasourceConstants";
+import { AxiosResponse } from "axios";
const Wrapper = styled.div`
display: flex;
justify-content: center;
@@ -154,16 +159,44 @@ type DatasourcePathParams = {
export const DatasourceEditPage = () => {
const { datasourceId, datasourceType } = useParams
();
- const datasourceList = useSelector(getDataSource);
const datasourceTypes = useSelector(getDataSourceTypes);
const [isReady, setIsReady] = useState(true);
- const datasourceInfo = useMemo(() => {
+
+ const [datasourceInfo, setDatasourceInfo] = useState();
+ const [loading, setLoading] = useState(false);
+
+ // Fetch individual datasource when editing
+ useEffect(() => {
if (!datasourceId) {
- return undefined;
+ setDatasourceInfo(undefined);
+ return;
}
- return datasourceList.find((info) => info.datasource.id === datasourceId);
- }, [datasourceId, datasourceList]);
+
+ const fetchDatasource = async () => {
+ setLoading(true);
+ try {
+ const response: AxiosResponse> = await DatasourceApi.getDatasourceById(datasourceId);
+ if (response.data.success) {
+ // Transform to DatasourceInfo format
+ setDatasourceInfo({
+ datasource: response.data.data,
+ edit: true, // Assume editable since user reached edit page
+ });
+ } else {
+ console.error('API returned error:', response.data);
+ setDatasourceInfo(undefined);
+ }
+ } catch (error: any) {
+ console.error('Failed to fetch datasource:', error);
+ setDatasourceInfo(undefined);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchDatasource();
+ }, [datasourceId]);
const dataSourceTypeInfo = useMemo(() => {
if (datasourceId) {
@@ -181,6 +214,26 @@ export const DatasourceEditPage = () => {
setIsReady(isReady);
}, []);
+ // Show loading state while fetching datasource
+ if (loading) {
+ return (
+
+
+
+
+
+
+
+ );
+ }
+
if (!finalDataSourceType) {
return null;
}
diff --git a/client/packages/lowcoder/src/pages/datasource/datasourceList.tsx b/client/packages/lowcoder/src/pages/datasource/datasourceList.tsx
index 2d7386fa09..cc83e64e63 100644
--- a/client/packages/lowcoder/src/pages/datasource/datasourceList.tsx
+++ b/client/packages/lowcoder/src/pages/datasource/datasourceList.tsx
@@ -1,8 +1,9 @@
import styled from "styled-components";
import { EditPopover, PointIcon, Search, TacoButton } from "lowcoder-design";
-import React, {useEffect, useState} from "react";
+import { useState, useEffect } from "react";
+import { useDebouncedValue } from "util/hooks";
import { useDispatch, useSelector } from "react-redux";
-import { getDataSource, getDataSourceLoading, getDataSourceTypesMap } from "../../redux/selectors/datasourceSelectors";
+import { getDataSourceTypesMap } from "../../redux/selectors/datasourceSelectors";
import { deleteDatasource } from "../../redux/reduxActions/datasourceActions";
import { isEmpty } from "lodash";
import history from "../../util/history";
@@ -113,7 +114,6 @@ export const DatasourceList = () => {
const [modify, setModify] = useState(false);
const currentUser = useSelector(getUser);
const orgId = currentUser.currentOrgId;
- const datasourceLoading = useSelector(getDataSourceLoading);
const plugins = useSelector(getDataSourceTypesMap);
interface ElementsState {
elements: DatasourceInfo[];
@@ -123,16 +123,18 @@ export const DatasourceList = () => {
const [elements, setElements] = useState({ elements: [], total: 0 });
const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(10);
+ const [paginationLoading, setPaginationLoading] = useState(false);
- useEffect(()=> {
- const timer = setTimeout(() => {
- if (searchValue.length > 2 || searchValue === "")
- setSearchValues(searchValue)
- }, 500);
- return () => clearTimeout(timer);
- }, [searchValue])
+ const debouncedSearchValue = useDebouncedValue(searchValue, 500);
+
+ useEffect(() => {
+ if (debouncedSearchValue.trim().length > 0 || debouncedSearchValue === "") {
+ setSearchValues(debouncedSearchValue);
+ }
+ }, [debouncedSearchValue]);
useEffect( () => {
+ setPaginationLoading(true);
fetchDatasourcePagination(
{
orgId: orgId,
@@ -146,6 +148,8 @@ export const DatasourceList = () => {
}
else
console.error("ERROR: fetchFolderElements", result.error)
+ }).finally(() => {
+ setPaginationLoading(false);
})
}, [currentPage, pageSize, searchValues, modify]
)
@@ -195,7 +199,7 @@ export const DatasourceList = () => {
}}
rowClassName={(record: any) => (!record.edit ? "datasource-can-not-edit" : "")}
diff --git a/client/packages/lowcoder/src/pages/datasource/index.tsx b/client/packages/lowcoder/src/pages/datasource/index.tsx
index fe965aba6c..9364af3919 100644
--- a/client/packages/lowcoder/src/pages/datasource/index.tsx
+++ b/client/packages/lowcoder/src/pages/datasource/index.tsx
@@ -8,26 +8,19 @@ import {
} from "../../constants/routesURL";
import React, { useEffect } from "react";
import { isEmpty } from "lodash";
-import { fetchDatasource, fetchDataSourceTypes } from "../../redux/reduxActions/datasourceActions";
+import { fetchDataSourceTypes } from "../../redux/reduxActions/datasourceActions";
import { useDispatch, useSelector } from "react-redux";
import { getUser } from "../../redux/selectors/usersSelectors";
-import { getDataSource, getDataSourceTypes } from "../../redux/selectors/datasourceSelectors";
+import { getDataSourceTypes } from "../../redux/selectors/datasourceSelectors";
export const DatasourceHome = () => {
const dispatch = useDispatch();
- const datasourceList = useSelector(getDataSource);
const datasourceTypes = useSelector(getDataSourceTypes);
const currentUser = useSelector(getUser);
const orgId = currentUser.currentOrgId;
- useEffect(() => {
- if (isEmpty(orgId) || datasourceList.length !== 0) {
- return;
- }
- dispatch(fetchDatasource({ organizationId: orgId }));
- }, [dispatch, datasourceList.length, orgId]);
useEffect(() => {
if (isEmpty(orgId) || datasourceTypes.length !== 0) {
diff --git a/client/packages/lowcoder/src/pages/editor/AppEditor.tsx b/client/packages/lowcoder/src/pages/editor/AppEditor.tsx
index 0928438920..9bdf356755 100644
--- a/client/packages/lowcoder/src/pages/editor/AppEditor.tsx
+++ b/client/packages/lowcoder/src/pages/editor/AppEditor.tsx
@@ -92,7 +92,7 @@ const AppEditor = React.memo(() => {
// Set global settings with cleanup
useEffect(() => {
- setGlobalSettings({ applicationId: selectors.applicationId, isViewMode: selectors.paramViewMode === "view" });
+ setGlobalSettings({ applicationId: selectors.applicationId, isViewMode: selectors.paramViewMode === "view" || selectors.paramViewMode === "view_marketplace" });
return () => {
clearGlobalSettings();
};
diff --git a/client/packages/lowcoder/src/pages/editor/LeftContent.tsx b/client/packages/lowcoder/src/pages/editor/LeftContent.tsx
index 126024d650..1b403d6823 100644
--- a/client/packages/lowcoder/src/pages/editor/LeftContent.tsx
+++ b/client/packages/lowcoder/src/pages/editor/LeftContent.tsx
@@ -446,7 +446,14 @@ export const LeftContent = (props: LeftContentProps) => {
{info?.show && data && (
+ {data.name}
+
+ Type: {data.type}
+
+
+ }
open={info.show}
onOk={() => setShowData([])}
cancelButtonProps={{ style: { display: 'none' } }}
diff --git a/client/packages/lowcoder/src/pages/editor/editorConstants.tsx b/client/packages/lowcoder/src/pages/editor/editorConstants.tsx
index a931455d4b..beea9cae7a 100644
--- a/client/packages/lowcoder/src/pages/editor/editorConstants.tsx
+++ b/client/packages/lowcoder/src/pages/editor/editorConstants.tsx
@@ -104,6 +104,7 @@ import {
TurnstileCaptchaCompIconSmall,
PivotTableCompIconSmall,
GraphChartCompIconSmall,
+ TagsCompIconSmall,
} from "lowcoder-design";
// Memoize icon components to prevent unnecessary re-renders
@@ -237,6 +238,7 @@ export const CompStateIcon: {
step: