Skip to content

Commit 50db580

Browse files
authored
Merge pull request #1547 from lowcoder-org/feature-iconscout-intergration
Feature - iconscout intergration
2 parents 7fbe869 + 9aaa4be commit 50db580

File tree

13 files changed

+1185
-224
lines changed

13 files changed

+1185
-224
lines changed

client/packages/lowcoder/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"@fortawesome/free-regular-svg-icons": "^6.5.1",
2525
"@fortawesome/free-solid-svg-icons": "^6.5.1",
2626
"@fortawesome/react-fontawesome": "latest",
27+
"@lottiefiles/dotlottie-react": "^0.13.0",
2728
"@manaflair/redux-batch": "^1.0.0",
2829
"@rjsf/antd": "^5.21.2",
2930
"@rjsf/core": "^5.21.2",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import Api from "api/api";
2+
import axios, { AxiosInstance, AxiosPromise, AxiosRequestConfig } from "axios";
3+
import { calculateFlowCode } from "./apiUtils";
4+
5+
export interface SearchParams {
6+
query: string;
7+
asset: string;
8+
per_page: number;
9+
page: 1;
10+
sort: string;
11+
formats?: string;
12+
price?: string;
13+
}
14+
15+
export type ResponseType = {
16+
response: any;
17+
};
18+
19+
const lcHeaders = {
20+
"Lowcoder-Token": calculateFlowCode(),
21+
"Content-Type": "application/json"
22+
};
23+
24+
let axiosIns: AxiosInstance | null = null;
25+
26+
const getAxiosInstance = (clientSecret?: string) => {
27+
if (axiosIns && !clientSecret) {
28+
return axiosIns;
29+
}
30+
31+
const headers: Record<string, string> = {
32+
"Content-Type": "application/json",
33+
};
34+
35+
const apiRequestConfig: AxiosRequestConfig = {
36+
baseURL: "https://api-service.lowcoder.cloud/api/flow",
37+
headers,
38+
};
39+
40+
axiosIns = axios.create(apiRequestConfig);
41+
return axiosIns;
42+
}
43+
44+
class IconFlowApi extends Api {
45+
46+
static async secureRequest(body: any, timeout: number = 6000): Promise<any> {
47+
let response;
48+
const axiosInstance = getAxiosInstance();
49+
50+
// Create a cancel token and set timeout for cancellation
51+
const source = axios.CancelToken.source();
52+
const timeoutId = setTimeout(() => {
53+
source.cancel("Request timed out.");
54+
}, timeout);
55+
56+
// Request configuration with cancel token
57+
const requestConfig: AxiosRequestConfig = {
58+
method: "POST",
59+
withCredentials: true,
60+
data: body,
61+
cancelToken: source.token, // Add cancel token
62+
};
63+
64+
try {
65+
response = await axiosInstance.request(requestConfig);
66+
} catch (error) {
67+
if (axios.isCancel(error)) {
68+
// Retry once after timeout cancellation
69+
try {
70+
// Reset the cancel token and retry
71+
const retrySource = axios.CancelToken.source();
72+
const retryTimeoutId = setTimeout(() => {
73+
retrySource.cancel("Retry request timed out.");
74+
}, 20000);
75+
76+
response = await axiosInstance.request({
77+
...requestConfig,
78+
cancelToken: retrySource.token,
79+
});
80+
81+
clearTimeout(retryTimeoutId);
82+
} catch (retryError) {
83+
console.warn("Error at Secure Flow Request. Retry failed:", retryError);
84+
throw retryError;
85+
}
86+
} else {
87+
console.warn("Error at Secure Flow Request:", error);
88+
throw error;
89+
}
90+
} finally {
91+
clearTimeout(timeoutId); // Clear the initial timeout
92+
}
93+
94+
return response;
95+
}
96+
97+
}
98+
99+
export const searchAssets = async (searchParameters : SearchParams) => {
100+
const apiBody = {
101+
path: "webhook/scout/search-asset",
102+
data: searchParameters,
103+
method: "post",
104+
headers: lcHeaders
105+
};
106+
try {
107+
const result = await IconFlowApi.secureRequest(apiBody);
108+
return result?.data?.response?.items?.total > 0 ? result.data.response.items as any : null;
109+
} catch (error) {
110+
console.error("Error searching Design Assets:", error);
111+
throw error;
112+
}
113+
};
114+
115+
export const getAssetLinks = async (uuid: string, params: Record<string, string>) => {
116+
const apiBody = {
117+
path: "webhook/scout/get-asset-links",
118+
data: {"uuid" : uuid, "params" : params},
119+
method: "post",
120+
headers: lcHeaders
121+
};
122+
try {
123+
const result = await IconFlowApi.secureRequest(apiBody);
124+
125+
return result?.data?.response?.download?.url.length > 0 ? result.data.response.download as any : null;
126+
} catch (error) {
127+
console.error("Error searching Design Assets:", error);
128+
throw error;
129+
}
130+
};
131+
132+
133+
/*
134+
135+
static async search(params: SearchParams): Promise<any> {
136+
let response;
137+
try {
138+
response = await getAxiosInstance().request({
139+
url: '/v3/search',
140+
method: "GET",
141+
withCredentials: false,
142+
params: {
143+
...params,
144+
},
145+
});
146+
} catch (error) {
147+
console.error(error);
148+
}
149+
return response?.data.response.items;
150+
}
151+
152+
static async download(uuid: string, params: Record<string, string>): Promise<any> {
153+
const response = await getAxiosInstance(clientSecret).request({
154+
url: `/v3/items/${uuid}/api-download?format=${params.format}`,
155+
method: "POST",
156+
withCredentials: false,
157+
});
158+
return response?.data.response.download;
159+
}
160+
161+
*/
162+
163+
export default IconFlowApi;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import Api from "api/api";
2+
import axios from "axios";
3+
4+
export type ResponseType = {
5+
response: any;
6+
};
7+
8+
class IconScoutApi extends Api {
9+
static async downloadAsset(url: string): Promise<any> {
10+
const response = await axios.get(url, {responseType: 'blob'})
11+
return response?.data;
12+
}
13+
}
14+
15+
export default IconScoutApi;

client/packages/lowcoder/src/api/subscriptionApi.ts

-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
import Api from "api/api";
22
import axios, { AxiosInstance, AxiosRequestConfig, CancelToken } from "axios";
3-
import { useDispatch, useSelector } from "react-redux";
4-
import { useEffect, useState} from "react";
53
import { calculateFlowCode } from "./apiUtils";
6-
import { fetchGroupsAction, fetchOrgUsersAction } from "redux/reduxActions/orgActions";
7-
import { getOrgUsers } from "redux/selectors/orgSelectors";
8-
import { AppState } from "@lowcoder-ee/redux/reducers";
94
import type {
105
LowcoderNewCustomer,
116
LowcoderSearchCustomer,

client/packages/lowcoder/src/app.tsx

+30-27
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import GlobalInstances from 'components/GlobalInstances';
6060
import { fetchHomeData, fetchServerSettingsAction } from "./redux/reduxActions/applicationActions";
6161
import { getNpmPackageMeta } from "./comps/utils/remote";
6262
import { packageMetaReadyAction, setLowcoderCompsLoading } from "./redux/reduxActions/npmPluginActions";
63+
import { SimpleSubscriptionContextProvider } from "./util/context/SimpleSubscriptionContext";
6364

6465
const LazyUserAuthComp = React.lazy(() => import("pages/userAuth"));
6566
const LazyInviteLanding = React.lazy(() => import("pages/common/inviteLanding"));
@@ -310,33 +311,35 @@ class AppIndex extends React.Component<AppIndexProps, any> {
310311
component={LazyPublicAppEditor}
311312
/>
312313

313-
<LazyRoute
314-
fallback="layout"
315-
path={APP_EDITOR_URL}
316-
component={LazyAppEditor}
317-
/>
318-
<LazyRoute
319-
fallback="layout"
320-
path={[
321-
USER_PROFILE_URL,
322-
NEWS_URL,
323-
ORG_HOME_URL,
324-
ALL_APPLICATIONS_URL,
325-
DATASOURCE_CREATE_URL,
326-
DATASOURCE_EDIT_URL,
327-
DATASOURCE_URL,
328-
SUPPORT_URL,
329-
QUERY_LIBRARY_URL,
330-
FOLDERS_URL,
331-
FOLDER_URL,
332-
TRASH_URL,
333-
SETTING_URL,
334-
MARKETPLACE_URL,
335-
ADMIN_APP_URL
336-
]}
337-
// component={ApplicationListPage}
338-
component={LazyApplicationHome}
339-
/>
314+
<SimpleSubscriptionContextProvider>
315+
<LazyRoute
316+
fallback="layout"
317+
path={APP_EDITOR_URL}
318+
component={LazyAppEditor}
319+
/>
320+
<LazyRoute
321+
fallback="layout"
322+
path={[
323+
USER_PROFILE_URL,
324+
NEWS_URL,
325+
ORG_HOME_URL,
326+
ALL_APPLICATIONS_URL,
327+
DATASOURCE_CREATE_URL,
328+
DATASOURCE_EDIT_URL,
329+
DATASOURCE_URL,
330+
SUPPORT_URL,
331+
QUERY_LIBRARY_URL,
332+
FOLDERS_URL,
333+
FOLDER_URL,
334+
TRASH_URL,
335+
SETTING_URL,
336+
MARKETPLACE_URL,
337+
ADMIN_APP_URL
338+
]}
339+
// component={ApplicationListPage}
340+
component={LazyApplicationHome}
341+
/>
342+
</SimpleSubscriptionContextProvider>
340343
<LazyRoute exact path={ADMIN_AUTH_URL} component={LazyUserAuthComp} />
341344
<LazyRoute path={USER_AUTH_URL} component={LazyUserAuthComp} />
342345
<LazyRoute

client/packages/lowcoder/src/comps/comps/iconComp.tsx

+21-3
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ import {
3030
} from "../controls/eventHandlerControl";
3131
import { useContext } from "react";
3232
import { EditorContext } from "comps/editorState";
33+
import { AssetType, IconscoutControl } from "@lowcoder-ee/comps/controls/iconscoutControl";
34+
import { dropdownControl } from "../controls/dropdownControl";
3335

3436
const Container = styled.div<{
3537
$style: IconStyleType | undefined;
@@ -61,10 +63,17 @@ const Container = styled.div<{
6163

6264
const EventOptions = [clickEvent] as const;
6365

66+
const ModeOptions = [
67+
{ label: "Standard", value: "standard" },
68+
{ label: "Asset Library", value: "asset-library" },
69+
] as const;
70+
6471
const childrenMap = {
6572
style: styleControl(IconStyle,'style'),
6673
animationStyle: styleControl(AnimationStyle,'animationStyle'),
74+
sourceMode: dropdownControl(ModeOptions, "standard"),
6775
icon: withDefault(IconControl, "/icon:antd/homefilled"),
76+
iconScoutAsset: IconscoutControl(AssetType.ICON),
6877
autoHeight: withDefault(AutoHeightControl, "auto"),
6978
iconSize: withDefault(NumberControl, 20),
7079
onEvent: eventHandlerControl(EventOptions),
@@ -103,7 +112,10 @@ const IconView = (props: RecordConstructorToView<typeof childrenMap>) => {
103112
}}
104113
onClick={() => props.onEvent("click")}
105114
>
106-
{props.icon}
115+
{ props.sourceMode === 'standard'
116+
? props.icon
117+
: <img src={props.iconScoutAsset.value} />
118+
}
107119
</Container>
108120
)}
109121
>
@@ -117,11 +129,17 @@ let IconBasicComp = (function () {
117129
.setPropertyViewFn((children) => (
118130
<>
119131
<Section name={sectionNames.basic}>
120-
{children.icon.propertyView({
132+
{ children.sourceMode.propertyView({
133+
label: "",
134+
radioButton: true
135+
})}
136+
{children.sourceMode.getView() === 'standard' && children.icon.propertyView({
121137
label: trans("iconComp.icon"),
122138
IconType: "All",
123139
})}
124-
140+
{children.sourceMode.getView() === 'asset-library' && children.iconScoutAsset.propertyView({
141+
label: trans("button.icon"),
142+
})}
125143
</Section>
126144

127145
{["logic", "both"].includes(useContext(EditorContext).editorModeStatus) && (

client/packages/lowcoder/src/comps/comps/imageComp.tsx

+22-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
withExposingConfigs,
1313
} from "../generators/withExposing";
1414
import { RecordConstructorToView } from "lowcoder-core";
15-
import { useEffect, useRef, useState } from "react";
15+
import { ReactElement, useEffect, useRef, useState } from "react";
1616
import _ from "lodash";
1717
import ReactResizeDetector from "react-resize-detector";
1818
import { styleControl } from "comps/controls/styleControl";
@@ -35,6 +35,8 @@ import { useContext } from "react";
3535
import { EditorContext } from "comps/editorState";
3636
import { StringControl } from "../controls/codeControl";
3737
import { PositionControl } from "comps/controls/dropdownControl";
38+
import { dropdownControl } from "../controls/dropdownControl";
39+
import { AssetType, IconscoutControl } from "../controls/iconscoutControl";
3840

3941
const Container = styled.div<{
4042
$style: ImageStyleType | undefined,
@@ -111,6 +113,10 @@ const getStyle = (style: ImageStyleType) => {
111113
};
112114

113115
const EventOptions = [clickEvent] as const;
116+
const ModeOptions = [
117+
{ label: "URL", value: "standard" },
118+
{ label: "Asset Library", value: "asset-library" },
119+
] as const;
114120

115121
const ContainerImg = (props: RecordConstructorToView<typeof childrenMap>) => {
116122
const imgRef = useRef<HTMLDivElement>(null);
@@ -194,7 +200,11 @@ const ContainerImg = (props: RecordConstructorToView<typeof childrenMap>) => {
194200
}
195201
>
196202
<AntImage
197-
src={props.src.value}
203+
src={
204+
props.sourceMode === 'asset-library'
205+
? props.iconScoutAsset?.value
206+
: props.src.value
207+
}
198208
referrerPolicy="same-origin"
199209
draggable={false}
200210
preview={props.supportPreview ? {src: props.previewSrc || props.src.value } : false}
@@ -210,7 +220,9 @@ const ContainerImg = (props: RecordConstructorToView<typeof childrenMap>) => {
210220
};
211221

212222
const childrenMap = {
223+
sourceMode: dropdownControl(ModeOptions, "standard"),
213224
src: withDefault(StringStateControl, "https://temp.im/350x400"),
225+
iconScoutAsset: IconscoutControl(AssetType.ILLUSTRATION),
214226
onEvent: eventHandlerControl(EventOptions),
215227
style: styleControl(ImageStyle , 'style'),
216228
animationStyle: styleControl(AnimationStyle , 'animationStyle'),
@@ -234,7 +246,14 @@ let ImageBasicComp = new UICompBuilder(childrenMap, (props) => {
234246
return (
235247
<>
236248
<Section name={sectionNames.basic}>
237-
{children.src.propertyView({
249+
{ children.sourceMode.propertyView({
250+
label: "",
251+
radioButton: true
252+
})}
253+
{children.sourceMode.getView() === 'standard' && children.src.propertyView({
254+
label: trans("image.src"),
255+
})}
256+
{children.sourceMode.getView() === 'asset-library' && children.iconScoutAsset.propertyView({
238257
label: trans("image.src"),
239258
})}
240259
</Section>

0 commit comments

Comments
 (0)