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

Lines changed: 1 addition & 0 deletions
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",
Lines changed: 163 additions & 0 deletions
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;
Lines changed: 15 additions & 0 deletions
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

Lines changed: 0 additions & 5 deletions
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

Lines changed: 30 additions & 27 deletions
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

Lines changed: 21 additions & 3 deletions
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

Lines changed: 22 additions & 3 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy