Skip to content

Commit 17641cd

Browse files
authored
Merge branch 'dev' into ui/environments-breadcrumbs
2 parents 17f3c7d + cda5657 commit 17641cd

File tree

3 files changed

+138
-126
lines changed

3 files changed

+138
-126
lines changed

client/packages/lowcoder/src/app.tsx

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@ import {
3131
ADMIN_AUTH_URL,
3232
PUBLIC_APP_EDITOR_URL,
3333
} from "constants/routesURL";
34-
import React, { useMemo } from "react";
34+
import React, { useEffect, useMemo } from "react";
3535
import { createRoot } from "react-dom/client";
3636
import { Helmet } from "react-helmet";
37-
import { connect, Provider } from "react-redux";
37+
import { connect, Provider, useDispatch, useSelector } from "react-redux";
3838
import { Redirect, Route, Router, Switch } from "react-router-dom";
3939
import type { AppState } from "redux/reducers";
40-
import { fetchConfigAction } from "redux/reduxActions/configActions";
40+
import { fetchConfigAction, fetchDeploymentIdAction } from "redux/reduxActions/configActions";
4141
import { fetchUserAction } from "redux/reduxActions/userActions";
4242
import { reduxStore } from "redux/store/store";
4343
import { developEnv } from "util/envUtils";
@@ -50,10 +50,10 @@ import { loadComps } from "comps";
5050
import { initApp } from "util/commonUtils";
5151
import { favicon } from "assets/images";
5252
import { hasQueryParam } from "util/urlUtils";
53-
import { isFetchUserFinished } from "redux/selectors/usersSelectors"; // getCurrentUser,
53+
import { getUser, isFetchUserFinished } from "redux/selectors/usersSelectors"; // getCurrentUser,
5454
import { getIsCommonSettingFetched } from "redux/selectors/commonSettingSelectors";
5555
import { SystemWarning } from "./components/SystemWarning";
56-
import { getBrandingConfig } from "./redux/selectors/configSelectors";
56+
import { getBrandingConfig, getDeploymentId } from "./redux/selectors/configSelectors";
5757
import { buildMaterialPreviewURL } from "./util/materialUtils";
5858
import GlobalInstances from 'components/GlobalInstances';
5959
// import posthog from 'posthog-js'
@@ -64,6 +64,7 @@ import { fetchBrandingSetting } from "./redux/reduxActions/enterpriseActions";
6464
import { EnterpriseProvider } from "./util/context/EnterpriseContext";
6565
import { SimpleSubscriptionContextProvider } from "./util/context/SimpleSubscriptionContext";
6666
import { getBrandingSetting } from "./redux/selectors/enterpriseSelectors";
67+
import { fetchSubscriptionsAction } from "./redux/reduxActions/subscriptionActions";
6768

6869
const LazyUserAuthComp = React.lazy(() => import("pages/userAuth"));
6970
const LazyInviteLanding = React.lazy(() => import("pages/common/inviteLanding"));
@@ -81,6 +82,22 @@ const Wrapper = React.memo((props: {
8182
language: string,
8283
fontFamily?: string
8384
}) => {
85+
const deploymentId = useSelector(getDeploymentId);
86+
const user = useSelector(getUser);
87+
const dispatch = useDispatch();
88+
89+
useEffect(() => {
90+
if (user.currentOrgId) {
91+
dispatch(fetchDeploymentIdAction());
92+
}
93+
}, [user.currentOrgId]);
94+
95+
useEffect(() => {
96+
if(Boolean(deploymentId)) {
97+
dispatch(fetchSubscriptionsAction())
98+
}
99+
}, [deploymentId]);
100+
84101
const theme = useMemo(() => {
85102
return {
86103
hashed: false,

client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx

Lines changed: 116 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
useIcon,
1313
wrapperToControlItem,
1414
} from "lowcoder-design";
15-
import { memo, ReactNode, useCallback, useMemo, useRef, useState } from "react";
15+
import { ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react";
1616
import styled from "styled-components";
1717
import Popover from "antd/es/popover";
1818
import { CloseIcon, SearchIcon } from "icons";
@@ -225,62 +225,85 @@ export const IconPicker = (props: {
225225
IconType?: "OnlyAntd" | "All" | "default" | undefined;
226226
}) => {
227227
const draggableRef = useRef<HTMLDivElement>(null);
228-
const [ visible, setVisible ] = useState(false)
229-
const [ loading, setLoading ] = useState(false)
230-
const [ downloading, setDownloading ] = useState(false)
231-
const [ searchText, setSearchText ] = useState<string>('')
232-
const [ searchResults, setSearchResults ] = useState<Array<any>>([]);
233-
const { subscriptions } = useSimpleSubscriptionContext();
234-
228+
const [visible, setVisible] = useState(false);
229+
const [loading, setLoading] = useState(false);
230+
const [downloading, setDownloading] = useState(false);
231+
const [searchText, setSearchText] = useState<string>('');
232+
const [searchResults, setSearchResults] = useState<Array<any>>([]);
235233
const [page, setPage] = useState(1);
236234
const [hasMore, setHasMore] = useState(true);
235+
const abortControllerRef = useRef<AbortController | null>(null);
236+
const { subscriptions } = useSimpleSubscriptionContext();
237237

238-
239-
const mediaPackSubscription = subscriptions.find(
240-
sub => sub.product === SubscriptionProductsEnum.MEDIAPACKAGE && sub.status === 'active'
238+
const mediaPackSubscription = useMemo(() =>
239+
subscriptions.find(
240+
sub => sub.product === SubscriptionProductsEnum.MEDIAPACKAGE && sub.status === 'active'
241+
),
242+
[subscriptions]
241243
);
242244

243245
const onChangeRef = useRef(props.onChange);
244246
onChangeRef.current = props.onChange;
245247

248+
// Cleanup function for async operations
249+
useEffect(() => {
250+
return () => {
251+
if (abortControllerRef.current) {
252+
abortControllerRef.current.abort();
253+
}
254+
};
255+
}, []);
256+
246257
const onChangeIcon = useCallback(
247258
(key: string, value: string, url: string) => {
248259
onChangeRef.current(key, value, url);
249260
setVisible(false);
250-
}, []
261+
},
262+
[]
251263
);
252264

253-
const fetchResults = async (query: string, pageNum: number = 1) => {
265+
const fetchResults = useCallback(async (query: string, pageNum: number = 1) => {
266+
if (abortControllerRef.current) {
267+
abortControllerRef.current.abort();
268+
}
269+
abortControllerRef.current = new AbortController();
270+
254271
setLoading(true);
272+
try {
273+
const [freeResult, premiumResult] = await Promise.all([
274+
searchAssets({
275+
...IconScoutSearchParams,
276+
asset: props.assetType,
277+
price: 'free',
278+
query,
279+
page: pageNum,
280+
}),
281+
searchAssets({
282+
...IconScoutSearchParams,
283+
asset: props.assetType,
284+
price: 'premium',
285+
query,
286+
page: pageNum,
287+
})
288+
]);
255289

256-
const freeResult = await searchAssets({
257-
...IconScoutSearchParams,
258-
asset: props.assetType,
259-
price: 'free',
260-
query,
261-
page: pageNum,
262-
});
263-
264-
const premiumResult = await searchAssets({
265-
...IconScoutSearchParams,
266-
asset: props.assetType,
267-
price: 'premium',
268-
query,
269-
page: pageNum,
270-
});
271-
272-
const combined = [...freeResult.data, ...premiumResult.data];
273-
const isLastPage = combined.length < IconScoutSearchParams.per_page * 2;
274-
275-
setSearchResults(prev =>
276-
pageNum === 1 ? combined : [...prev, ...combined]
277-
);
278-
setHasMore(!isLastPage);
279-
setLoading(false);
280-
};
281-
290+
const combined = [...freeResult.data, ...premiumResult.data];
291+
const isLastPage = combined.length < IconScoutSearchParams.per_page * 2;
292+
293+
setSearchResults(prev =>
294+
pageNum === 1 ? combined : [...prev, ...combined]
295+
);
296+
setHasMore(!isLastPage);
297+
} catch (error: any) {
298+
if (error.name !== 'AbortError') {
299+
console.error('Error fetching results:', error);
300+
}
301+
} finally {
302+
setLoading(false);
303+
}
304+
}, [props.assetType]);
282305

283-
const downloadAsset = async (
306+
const downloadAsset = useCallback(async (
284307
uuid: string,
285308
downloadUrl: string,
286309
callback: (assetUrl: string) => void,
@@ -293,29 +316,29 @@ export const IconPicker = (props: {
293316
});
294317
}
295318
} catch(error) {
296-
console.error(error);
319+
console.error('Error downloading asset:', error);
297320
setDownloading(false);
298321
}
299-
}
322+
}, []);
300323

301-
const fetchDownloadUrl = async (uuid: string, preview: string) => {
324+
const fetchDownloadUrl = useCallback(async (uuid: string, preview: string) => {
302325
try {
303326
setDownloading(true);
304327
const result = await getAssetLinks(uuid, {
305328
format: props.assetType === AssetType.LOTTIE ? 'lottie' : 'svg',
306329
});
307330

308-
downloadAsset(uuid, result.download_url, (assetUrl: string) => {
331+
await downloadAsset(uuid, result.download_url, (assetUrl: string) => {
309332
setDownloading(false);
310333
onChangeIcon(uuid, assetUrl, preview);
311334
});
312335
} catch (error) {
313-
console.error(error);
336+
console.error('Error fetching download URL:', error);
314337
setDownloading(false);
315338
}
316-
}
339+
}, [props.assetType, downloadAsset, onChangeIcon]);
317340

318-
const handleChange = (e: { target: { value: any; }; }) => {
341+
const handleChange = useCallback((e: { target: { value: any; }; }) => {
319342
const query = e.target.value;
320343
setSearchText(query); // Update search text immediately
321344

@@ -324,9 +347,15 @@ export const IconPicker = (props: {
324347
} else {
325348
setSearchResults([]); // Clear results if input is too short
326349
}
327-
};
328-
329-
const debouncedFetchResults = useMemo(() => debounce(fetchResults, 700), []);
350+
}, []);
351+
352+
const debouncedFetchResults = useMemo(
353+
() => debounce((query: string) => {
354+
setPage(1);
355+
fetchResults(query, 1);
356+
}, 700),
357+
[fetchResults]
358+
);
330359

331360
const rowRenderer = useCallback(
332361
({ index, key, style }: ListRowProps) => {
@@ -408,39 +437,41 @@ export const IconPicker = (props: {
408437
</IconRow>
409438
);
410439
},
411-
[columnNum, mediaPackSubscription, props.assetType, fetchDownloadUrl]
440+
[columnNum, mediaPackSubscription, props.assetType, fetchDownloadUrl, searchResults]
412441
);
413-
414442

415443
const popupTitle = useMemo(() => {
416444
if (props.assetType === AssetType.ILLUSTRATION) return trans("iconScout.searchImage");
417445
if (props.assetType === AssetType.LOTTIE) return trans("iconScout.searchAnimation");
418446
return trans("iconScout.searchIcon");
419447
}, [props.assetType]);
420448

421-
const MemoizedIconList = memo(({
422-
searchResults,
423-
rowRenderer,
424-
onScroll,
425-
columnNum,
449+
const handleScroll = useCallback(({
450+
clientHeight,
451+
scrollHeight,
452+
scrollTop,
426453
}: {
427-
searchResults: any[];
428-
rowRenderer: (props: ListRowProps) => React.ReactNode;
429-
onScroll: (params: { clientHeight: number; scrollHeight: number; scrollTop: number }) => void;
430-
columnNum: number;
454+
clientHeight: number;
455+
scrollHeight: number;
456+
scrollTop: number;
431457
}) => {
432-
return (
433-
<IconList
434-
width={550}
435-
height={400}
436-
rowHeight={140}
437-
rowCount={Math.ceil(searchResults.length / columnNum)}
438-
rowRenderer={rowRenderer}
439-
onScroll={onScroll}
440-
/>
441-
);
442-
});
443-
458+
if (hasMore && !loading && scrollHeight - scrollTop <= clientHeight + 10) {
459+
const nextPage = page + 1;
460+
setPage(nextPage);
461+
fetchResults(searchText, nextPage);
462+
}
463+
}, [hasMore, loading, page, searchText, fetchResults]);
464+
465+
const memoizedIconListElement = useMemo(() => (
466+
<IconList
467+
width={550}
468+
height={400}
469+
rowHeight={140}
470+
rowCount={Math.ceil(searchResults.length / columnNum)}
471+
rowRenderer={rowRenderer}
472+
onScroll={handleScroll}
473+
/>
474+
), [searchResults.length, rowRenderer, handleScroll, columnNum]);
444475

445476
return (
446477
<Popover
@@ -471,11 +502,6 @@ export const IconPicker = (props: {
471502
/>
472503
<StyledSearchIcon />
473504
</SearchDiv>
474-
{loading && (
475-
<Flex align="center" justify="center" style={{flex: 1}}>
476-
<Spin indicator={<LoadingOutlined style={{ fontSize: 25 }} spin />} />
477-
</Flex>
478-
)}
479505
<Spin spinning={downloading} indicator={<LoadingOutlined style={{ fontSize: 25 }} />} >
480506
{!loading && Boolean(searchText) && !Boolean(searchResults?.length) && (
481507
<Flex align="center" justify="center" style={{flex: 1}}>
@@ -484,33 +510,16 @@ export const IconPicker = (props: {
484510
</Typography.Text>
485511
</Flex>
486512
)}
487-
{!loading && Boolean(searchText) && Boolean(searchResults?.length) && (
513+
{Boolean(searchText) && Boolean(searchResults?.length) && (
488514
<IconListWrapper>
489-
490-
<IconList
491-
width={550}
492-
height={400}
493-
rowHeight={140}
494-
rowCount={Math.ceil(searchResults.length / columnNum)}
495-
rowRenderer={rowRenderer}
496-
onScroll={({
497-
clientHeight,
498-
scrollHeight,
499-
scrollTop,
500-
}: {
501-
clientHeight: number;
502-
scrollHeight: number;
503-
scrollTop: number;
504-
}) => {
505-
if (hasMore && !loading && scrollHeight - scrollTop <= clientHeight + 10) {
506-
const nextPage = page + 1;
507-
setPage(nextPage);
508-
fetchResults(searchText, nextPage);
509-
}
510-
}}
511-
/>
515+
{memoizedIconListElement}
512516
</IconListWrapper>
513517
)}
518+
{loading && (
519+
<Flex align="center" justify="center" style={{flex: 1}}>
520+
<Spin indicator={<LoadingOutlined style={{ fontSize: 25 }} spin />} />
521+
</Flex>
522+
)}
514523
</Spin>
515524
</PopupContainer>
516525
</Draggable>
@@ -557,11 +566,12 @@ export function IconscoutControl(
557566
) {
558567
return class IconscoutControl extends SimpleComp<IconScoutAsset> {
559568
readonly IGNORABLE_DEFAULT_VALUE = false;
569+
560570
protected getDefaultValue(): IconScoutAsset {
561571
return {
562-
uuid: '',
563-
value: '',
564-
preview: '',
572+
uuid: "",
573+
value: "",
574+
preview: "",
565575
};
566576
}
567577

@@ -586,5 +596,5 @@ export function IconscoutControl(
586596
</ControlPropertyViewWrapper>
587597
);
588598
}
589-
}
599+
};
590600
}

0 commit comments

Comments
 (0)