From 0639f1bc80bb27edf30fc3a1da8cee571e9fb62f Mon Sep 17 00:00:00 2001
From: RAHEEL <mraheeliftikhar1994@gmail.com>
Date: Tue, 2 Jul 2024 10:36:37 +0500
Subject: [PATCH 01/18] integrate iconscout in icon button and image comp

---
 .../packages/lowcoder/src/api/iconscoutApi.ts |  80 +++
 .../lowcoder/src/comps/comps/imageComp.tsx    |  25 +-
 .../comps/comps/jsonComp/jsonLottieComp.tsx   |  22 +-
 .../comps/comps/meetingComp/controlButton.tsx |  36 +-
 .../src/comps/controls/iconscoutControl.tsx   | 611 ++++++++++++++++++
 5 files changed, 766 insertions(+), 8 deletions(-)
 create mode 100644 client/packages/lowcoder/src/api/iconscoutApi.ts
 create mode 100644 client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx

diff --git a/client/packages/lowcoder/src/api/iconscoutApi.ts b/client/packages/lowcoder/src/api/iconscoutApi.ts
new file mode 100644
index 0000000000..8f947d88fb
--- /dev/null
+++ b/client/packages/lowcoder/src/api/iconscoutApi.ts
@@ -0,0 +1,80 @@
+import Api from "api/api";
+import axios, { AxiosInstance, AxiosPromise, AxiosRequestConfig } from "axios";
+import { GenericApiResponse } from "./apiResponses";
+
+export interface SearchParams {
+  query: string;
+  product_type: string;
+  asset: string;
+  per_page: number;
+  page: 1;
+  sort: string;
+  formats: string;
+}
+
+export type ResponseType = {
+  response: any;
+};
+
+const apiUrl = "https://api.iconscout.com";
+const clientID = ""; //"91870410585071";
+const clientSecret = ""; // "GV5aCWpwdLWTxVXFBjMKSoyDPUyjzXLR";
+const currentPage = 1;
+const currentQuery = '';
+const currentData = [];
+
+let axiosIns: AxiosInstance | null = null;
+
+const getAxiosInstance = (clientSecret?: string) => {
+  if (axiosIns && !clientSecret) {
+    return axiosIns;
+  }
+
+  const headers: Record<string, string> = {
+    "Content-Type": "application/json",
+    "Client-ID": clientID,
+  }
+  if (clientSecret) {
+    headers['Client-Secret'] = clientSecret;
+  }
+  const apiRequestConfig: AxiosRequestConfig = {
+    baseURL: `${apiUrl}`,
+    headers,
+    withCredentials: true,
+  };
+
+  axiosIns = axios.create(apiRequestConfig);
+  return axiosIns;
+}
+
+class IconscoutApi extends Api {
+  static async search(params: SearchParams): Promise<any> {
+    let response;
+    try {
+      response = await getAxiosInstance().request({
+        url: '/v3/search',
+        method: "GET",
+        withCredentials: false,
+        params: {
+          ...params,
+          'formats[]': params.formats,
+        },
+      });
+    } catch (error) {
+      console.error(error);
+    }
+    return response?.data.response.items;
+  }
+
+  static async download(uuid: string, params: Record<string, string>): Promise<any> {
+    const response = await getAxiosInstance(clientSecret).request({
+      url: `/v3/items/${uuid}/api-download`,
+      method: "POST",
+      withCredentials: false,
+      params,
+    });
+    return response?.data.response.download;
+  }
+}
+
+export default IconscoutApi;
diff --git a/client/packages/lowcoder/src/comps/comps/imageComp.tsx b/client/packages/lowcoder/src/comps/comps/imageComp.tsx
index d78a21d201..7456fc2ff5 100644
--- a/client/packages/lowcoder/src/comps/comps/imageComp.tsx
+++ b/client/packages/lowcoder/src/comps/comps/imageComp.tsx
@@ -12,7 +12,7 @@ import {
   withExposingConfigs,
 } from "../generators/withExposing";
 import { RecordConstructorToView } from "lowcoder-core";
-import { useEffect, useRef, useState } from "react";
+import { ReactElement, useEffect, useRef, useState } from "react";
 import _ from "lodash";
 import ReactResizeDetector from "react-resize-detector";
 import { styleControl } from "comps/controls/styleControl";
@@ -35,6 +35,8 @@ import { useContext } from "react";
 import { EditorContext } from "comps/editorState";
 import { StringControl } from "../controls/codeControl";
 import { PositionControl } from "comps/controls/dropdownControl";
+import { dropdownControl } from "../controls/dropdownControl";
+import { IconScoutAssetType, IconscoutControl } from "../controls/iconscoutControl";
 
 const Container = styled.div<{ 
   $style: ImageStyleType | undefined, 
@@ -111,6 +113,10 @@ const getStyle = (style: ImageStyleType) => {
 };
 
 const EventOptions = [clickEvent] as const;
+const ModeOptions = [
+  { label: "URL", value: "standard" },
+  { label: "Advanced", value: "advanced" },
+] as const;
 
 const ContainerImg = (props: RecordConstructorToView<typeof childrenMap>) => {
   const imgRef = useRef<HTMLDivElement>(null);
@@ -194,7 +200,11 @@ const ContainerImg = (props: RecordConstructorToView<typeof childrenMap>) => {
             }
           >
             <AntImage
-              src={props.src.value}
+              src={
+                props.sourceMode === 'advanced'
+                ? (props.srcIconScout as ReactElement)?.props.value
+                : props.src.value
+              }
               referrerPolicy="same-origin"
               draggable={false}
               preview={props.supportPreview ? {src: props.previewSrc || props.src.value } : false}
@@ -210,7 +220,9 @@ const ContainerImg = (props: RecordConstructorToView<typeof childrenMap>) => {
 };
 
 const childrenMap = {
+  sourceMode: dropdownControl(ModeOptions, "standard"),
   src: withDefault(StringStateControl, "https://temp.im/350x400"),
+  srcIconScout: IconscoutControl(IconScoutAssetType.ILLUSTRATION),
   onEvent: eventHandlerControl(EventOptions),
   style: styleControl(ImageStyle , 'style'),
   animationStyle: styleControl(AnimationStyle , 'animationStyle'),
@@ -234,7 +246,14 @@ let ImageBasicComp = new UICompBuilder(childrenMap, (props) => {
     return (
       <>
         <Section name={sectionNames.basic}>
-          {children.src.propertyView({
+          { children.sourceMode.propertyView({
+            label: "",
+            radioButton: true
+          })}
+          {children.sourceMode.getView() === 'standard' && children.src.propertyView({
+            label: trans("image.src"),
+          })}
+          {children.sourceMode.getView() === 'advanced' &&children.srcIconScout.propertyView({
             label: trans("image.src"),
           })}
         </Section>
diff --git a/client/packages/lowcoder/src/comps/comps/jsonComp/jsonLottieComp.tsx b/client/packages/lowcoder/src/comps/comps/jsonComp/jsonLottieComp.tsx
index c3f93b6e1c..cfaf06bca9 100644
--- a/client/packages/lowcoder/src/comps/comps/jsonComp/jsonLottieComp.tsx
+++ b/client/packages/lowcoder/src/comps/comps/jsonComp/jsonLottieComp.tsx
@@ -18,6 +18,7 @@ import {
 } from "../../generators/withExposing";
 import { defaultLottie } from "./jsonConstants";
 import { EditorContext } from "comps/editorState";
+import { IconScoutAssetType, IconscoutControl } from "@lowcoder-ee/comps/controls/iconscoutControl";
 
 const Player = lazy(
   () => import('@lottiefiles/react-lottie-player')
@@ -84,12 +85,20 @@ const speedOptions = [
   },
 ] as const;
 
+const ModeOptions = [
+  { label: "Data", value: "standard" },
+  { label: "Advanced", value: "advanced" },
+] as const;
+
 let JsonLottieTmpComp = (function () {
   const childrenMap = {
+    sourceMode: dropdownControl(ModeOptions, "standard"),
     value: withDefault(
       ArrayOrJSONObjectControl,
       JSON.stringify(defaultLottie, null, 2)
     ),
+    srcIconScout: IconscoutControl(IconScoutAssetType.LOTTIE),
+    valueIconScout: ArrayOrJSONObjectControl,
     speed: dropdownControl(speedOptions, "1"),
     width: withDefault(NumberControl, 100),
     height: withDefault(NumberControl, 100),
@@ -100,6 +109,7 @@ let JsonLottieTmpComp = (function () {
     keepLastFrame: BoolControl.DEFAULT_TRUE,
   };
   return new UICompBuilder(childrenMap, (props) => {
+    console.log(props.srcIconScout);
     return (
       <div
         style={{
@@ -145,7 +155,17 @@ let JsonLottieTmpComp = (function () {
       return (
         <>
           <Section name={sectionNames.basic}>
-            {children.value.propertyView({
+            { children.sourceMode.propertyView({
+              label: "",
+              radioButton: true
+            })}
+            {children.sourceMode.getView() === 'standard' && children.value.propertyView({
+              label: trans("jsonLottie.lottieJson"),
+            })}
+            {children.sourceMode.getView() === 'advanced' && children.srcIconScout.propertyView({
+              label: "Lottie Source",
+            })}
+            {children.sourceMode.getView() === 'advanced' && children.valueIconScout.propertyView({
               label: trans("jsonLottie.lottieJson"),
             })}
           </Section>
diff --git a/client/packages/lowcoder/src/comps/comps/meetingComp/controlButton.tsx b/client/packages/lowcoder/src/comps/comps/meetingComp/controlButton.tsx
index 28e318618c..f8edb43d61 100644
--- a/client/packages/lowcoder/src/comps/comps/meetingComp/controlButton.tsx
+++ b/client/packages/lowcoder/src/comps/comps/meetingComp/controlButton.tsx
@@ -39,6 +39,7 @@ import { useEffect, useRef, useState } from "react";
 import ReactResizeDetector from "react-resize-detector";
 
 import { useContext } from "react";
+import { IconScoutAssetType, IconscoutControl } from "@lowcoder-ee/comps/controls/iconscoutControl";
 
 const Container = styled.div<{ $style: any }>`
   height: 100%;
@@ -74,6 +75,13 @@ const IconWrapper = styled.div<{ $style: any }>`
   ${(props) => props.$style && getStyleIcon(props.$style)}
 `;
 
+const IconScoutWrapper = styled.div<{ $style: any }>`
+  display: flex;
+  height: 100%;
+
+  ${(props) => props.$style && getStyleIcon(props.$style)}
+`;
+
 function getStyleIcon(style: any) {
   return css`
     svg {
@@ -163,6 +171,11 @@ const typeOptions = [
   },
 ] as const;
 
+const ModeOptions = [
+  { label: "Standard", value: "standard" },
+  { label: "Advanced", value: "advanced" },
+] as const;
+
 function isDefault(type?: string) {
   return !type;
 }
@@ -183,7 +196,9 @@ const childrenMap = {
   disabled: BoolCodeControl,
   loading: BoolCodeControl,
   form: SelectFormControl,
+  sourceMode: dropdownControl(ModeOptions, "standard"),
   prefixIcon: IconControl,
+  prefixIconScout: IconscoutControl(IconScoutAssetType.ICON),
   style: ButtonStyleControl,
   viewRef: RefControl<HTMLElement>,
   restrictPaddingOnRotation:withDefault(StringControl, 'controlButton')
@@ -226,7 +241,7 @@ let ButtonTmpComp = (function () {
 
       setStyle(container?.clientHeight + "px", container?.clientWidth + "px");
     };
-
+    console.log(props.prefixIconScout);
     return (
       <EditorContext.Consumer>
         {(editorState) => (
@@ -270,14 +285,20 @@ let ButtonTmpComp = (function () {
                         : submitForm(editorState, props.form)
                     }
                   >
-                    {props.prefixIcon && (
+                    {props.sourceMode === 'standard' && props.prefixIcon && (
                       <IconWrapper
                         $style={{ ...props.style, size: props.iconSize }}
                       >
                         {props.prefixIcon}
                       </IconWrapper>
                     )}
-                    
+                    {props.sourceMode === 'advanced' && props.prefixIconScout && (
+                      <IconScoutWrapper
+                        $style={{ ...props.style, size: props.iconSize }}
+                      >
+                        {props.prefixIconScout}
+                      </IconScoutWrapper>
+                    )}
                   </Button100>
                 </div>
               </Container>
@@ -291,7 +312,14 @@ let ButtonTmpComp = (function () {
     .setPropertyViewFn((children) => (
       <>
         <Section name={sectionNames.basic}>
-          {children.prefixIcon.propertyView({
+          { children.sourceMode.propertyView({
+            label: "",
+            radioButton: true
+          })}
+          {children.sourceMode.getView() === 'standard' && children.prefixIcon.propertyView({
+            label: trans("button.icon"),
+          })}
+          {children.sourceMode.getView() === 'advanced' &&children.prefixIconScout.propertyView({
             label: trans("button.icon"),
           })}
         </Section>
diff --git a/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx b/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
new file mode 100644
index 0000000000..47cca644ff
--- /dev/null
+++ b/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
@@ -0,0 +1,611 @@
+import type { EditorState, EditorView } from "base/codeEditor/codeMirror";
+import { iconRegexp, iconWidgetClass } from "base/codeEditor/extensions/iconExtension";
+import { i18nObjs, trans } from "i18n";
+import {
+  AbstractComp,
+  CompAction,
+  CompActionTypes,
+  CompParams,
+  customAction,
+  DispatchType,
+  Node,
+  SimpleComp,
+  ValueAndMsg,
+} from "lowcoder-core";
+import {
+  BlockGrayLabel,
+  controlItem,
+  ControlPropertyViewWrapper,
+  DeleteInputIcon,
+  iconPrefix,
+  IconSelect,
+  IconSelectBase,
+  removeQuote,
+  SwitchJsIcon,
+  SwitchWrapper,
+  TacoButton,
+  TacoInput,
+  useIcon,
+  wrapperToControlItem,
+} from "lowcoder-design";
+import { ReactNode, SetStateAction, useCallback, useEffect, useRef, useState } from "react";
+import styled from "styled-components";
+import { setFieldsNoTypeCheck } from "util/objectUtils";
+import { StringControl } from "./codeControl";
+import { ControlParams } from "./controlParams";
+import Popover from "antd/es/popover";
+import { CloseIcon, SearchIcon } from "icons";
+import Draggable from "react-draggable";
+import IconscoutApi, { SearchParams } from "api/iconscoutApi";
+import List, { ListRowProps } from "react-virtualized/dist/es/List";
+import { debounce } from "lodash";
+import Spin from "antd/es/spin";
+
+const ButtonWrapper = styled.div`
+  width: 100%;
+  display: flex;
+  align-items: center;
+`;
+const ButtonIconWrapper = styled.div`
+  display: flex;
+  width: 18px;
+`;
+const ButtonText = styled.div`
+  margin: 0 4px;
+  flex: 1;
+  width: 0px;
+  line-height: 20px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  text-align: left;
+`;
+const StyledDeleteInputIcon = styled(DeleteInputIcon)`
+  margin-left: auto;
+  cursor: pointer;
+
+  &:hover circle {
+    fill: #8b8fa3;
+  }
+`;
+
+const StyledImage = styled.img`
+  height: 100%;
+  color: currentColor;
+`;
+
+const Wrapper = styled.div`
+  > div:nth-of-type(1) {
+    margin-bottom: 4px;
+  }
+`;
+const PopupContainer = styled.div`
+  width: 580px;
+  background: #ffffff;
+  box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
+  border-radius: 8px;
+  box-sizing: border-box;
+`;
+
+const TitleDiv = styled.div`
+  height: 48px;
+  display: flex;
+  align-items: center;
+  padding: 0 16px;
+  justify-content: space-between;
+  user-select: none;
+`;
+const TitleText = styled.span`
+  font-size: 16px;
+  color: #222222;
+  line-height: 16px;
+`;
+const StyledCloseIcon = styled(CloseIcon)`
+  width: 16px;
+  height: 16px;
+  cursor: pointer;
+  color: #8b8fa3;
+
+  &:hover g line {
+    stroke: #222222;
+  }
+`;
+
+const SearchDiv = styled.div`
+  position: relative;
+  margin: 0px 16px;
+  padding-bottom: 8px;
+  display: flex;
+  justify-content: space-between;
+`;
+const StyledSearchIcon = styled(SearchIcon)`
+  position: absolute;
+  top: 6px;
+  left: 12px;
+`;
+const IconListWrapper = styled.div`
+  padding-left: 10px;
+  padding-right: 4px;
+`;
+const IconList = styled(List)`
+  scrollbar-gutter: stable;
+
+  &::-webkit-scrollbar {
+    width: 6px;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    background-clip: content-box;
+    border-radius: 9999px;
+    background-color: rgba(139, 143, 163, 0.2);
+  }
+
+  &::-webkit-scrollbar-thumb:hover {
+    background-color: rgba(139, 143, 163, 0.36);
+  }
+`;
+
+const IconRow = styled.div`
+  padding: 0 6px;
+  display: flex;
+  align-items: flex-start; /* Align items to the start to allow different heights */
+  justify-content: space-between;
+
+  &:last-child {
+    gap: 8px;
+    justify-content: flex-start;
+  }
+`;
+
+const IconItemContainer = styled.div`
+  width: 60px;
+  display: flex;
+  flex-direction: column;
+  align-items: center; 
+  justify-content: flex-start; 
+  cursor: pointer;
+  font-size: 28px;
+  margin-bottom: 24px; 
+
+  &:hover {
+    border: 1px solid #315efb;
+    border-radius: 4px;
+  }
+
+  &:focus {
+    border: 1px solid #315efb;
+    border-radius: 4px;
+    box-shadow: 0 0 0 2px #d6e4ff;
+  }
+`;
+
+const IconWrapper = styled.div`
+  height: auto;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+`;
+
+const IconKeyDisplay = styled.div`
+  font-size: 8px;
+  color: #8b8fa3;
+  margin-top: 4px; /* Space between the icon and the text */
+  text-align: center;
+  word-wrap: break-word; /* Ensure text wraps */
+  width: 100%; /* Ensure the container can grow */
+`;
+
+export enum IconScoutAssetType {
+  ICON = "icon",
+  ILLUSTRATION = "illustration",
+  // '3D' = "3d",
+  LOTTIE = "lottie",
+}
+
+const IconScoutSearchParams: SearchParams = {
+  query: '',
+  product_type: 'item',
+  asset: 'icon',
+  per_page: 50,
+  page: 1,
+  formats: 'svg',
+  sort: 'relevant',
+};
+
+const columnNum = 8;
+
+export const IconPicker = (props: {
+  assetType: string;
+  value: string;
+  onChange: (value: string) => void;
+  label?: ReactNode;
+  IconType?: "OnlyAntd" | "All" | "default" | undefined;
+}) => {
+  console.log(props.value, props.assetType);
+  const icon = useIcon(props.value);
+  const [ visible, setVisible ] = useState(false)
+  const [ loading, setLoading ] = useState(false)
+  const [searchText, setSearchText] = useState("");
+  const [ searchResults, setSearchResults ] = useState<Array<any>>([]);
+  const onChangeRef = useRef(props.onChange);
+  onChangeRef.current = props.onChange;
+  const onChangeIcon = useCallback(
+    (key: string) => {
+      onChangeRef.current(key);
+      setVisible(false);
+    }, []
+  );
+
+  const fetchResults = async (query: string) => {
+    console.log('query change', query);
+    setLoading(true);
+    const result = await IconscoutApi.search({
+      ...IconScoutSearchParams,
+      asset: props.assetType,
+      query,
+    });
+    setLoading(false);
+    setSearchResults(result.data);
+  };
+
+  const fetchAsset = async (uuid: string) => {
+    try {
+      const result = await IconscoutApi.download(uuid, {
+        format: props.assetType === IconScoutAssetType.LOTTIE ? 'ai' : 'svg',
+      });
+      if (props.assetType !== IconScoutAssetType.LOTTIE) {
+        onChangeIcon(result.url);
+      }
+    } catch (error) {
+      console.error(error);
+    }
+  }
+
+  const handleChange = debounce((e) => {
+    setSearchText(e.target.value);
+    fetchResults(e.target.value);
+  }, 500);
+
+  const rowRenderer = useCallback(
+    (p: ListRowProps) => (
+      <IconRow key={p.key} style={p.style}>
+        {searchResults
+          .slice(p.index * columnNum, (p.index + 1) * columnNum)
+          .map((icon) => (
+            <IconItemContainer
+              key={icon.uuid}
+              tabIndex={0}
+              onClick={() => {
+                if (props.assetType === IconScoutAssetType.LOTTIE) {
+                  onChangeIcon(icon.urls.thumb)
+                }
+                fetchAsset(icon.uuid);
+              }}
+            >
+              <IconWrapper>
+                {props.assetType === IconScoutAssetType.ICON && (
+                  <img style={{'width': '100%'}} src={icon.urls.png_64} />
+                )}
+                {props.assetType === IconScoutAssetType.ILLUSTRATION && (
+                  <img style={{'width': '100%'}} src={icon.urls.thumb} />
+                )}
+                {props.assetType === IconScoutAssetType.LOTTIE && (
+                  <video style={{'width': '100%'}} src={icon.urls.thumb} autoPlay />
+                )}
+              </IconWrapper>
+            </IconItemContainer>
+          ))}
+      </IconRow>
+    ),[searchResults]
+  );
+
+  return (
+    <Popover
+      trigger={'click'}
+      placement="left"
+      // align={{ offset: [props.leftOffset ?? 0, 0, 0, 0] }}
+      open={visible}
+      onOpenChange={setVisible}
+      // getPopupContainer={parent ? () => parent : undefined}
+      // hide the original background when dragging the popover is allowed
+      overlayInnerStyle={{
+        border: "none",
+        boxShadow: "none",
+        background: "transparent",
+      }}
+      // when dragging is allowed, always re-location to avoid the popover exceeds the screen
+      destroyTooltipOnHide
+      content={
+        <Draggable handle=".dragHandle">
+          <PopupContainer>
+            <TitleDiv className="dragHandle">
+              <TitleText>{"Select Icon"}</TitleText>
+              <StyledCloseIcon onClick={() => setVisible(false)} />
+            </TitleDiv>
+            <SearchDiv>
+              <TacoInput
+                style={{ width: "100%", paddingLeft: "40px" }}
+                onChange={handleChange}
+                placeholder={"Search Icon"}
+              />
+              <StyledSearchIcon />
+            </SearchDiv>
+            <IconListWrapper>
+              {loading && (
+                <Spin />
+              )}
+              {!loading && (
+                <IconList
+                  width={550}
+                  height={400}
+                  rowHeight={80}
+                  rowCount={Math.ceil(searchResults.length / columnNum)}
+                  rowRenderer={rowRenderer}
+                />
+              )}
+            </IconListWrapper>
+          </PopupContainer>
+        </Draggable>
+      }
+    >
+      <TacoButton style={{ width: "100%" }}>
+        {props.value ? (
+          <ButtonWrapper>
+            <ButtonIconWrapper>
+              {props.assetType === IconScoutAssetType.LOTTIE && (
+                <>{props.value}</>
+              )}
+              {props.assetType !== IconScoutAssetType.LOTTIE && (
+                <IconControlView value={props.value} />
+              )}
+            </ButtonIconWrapper>
+            <StyledDeleteInputIcon
+              onClick={(e) => {
+                props.onChange("");
+                e.stopPropagation();
+              }}
+            />
+          </ButtonWrapper>
+        ) : (
+          <BlockGrayLabel label={trans("iconControl.selectIcon")} />
+        )}
+      </TacoButton>
+    </Popover>
+  );
+};
+
+function onClickIcon(e: React.MouseEvent, v: EditorView) {
+  for (let t = e.target as HTMLElement | null; t; t = t.parentElement) {
+    if (t.classList.contains(iconWidgetClass)) {
+      const pos = v.posAtDOM(t);
+      const result = iconRegexp.exec(v.state.doc.sliceString(pos));
+      if (result) {
+        const from = pos + result.index;
+        return { from, to: from + result[0].length };
+      }
+    }
+  }
+}
+
+function IconSpan(props: { value: string }) {
+  const icon = useIcon(props.value);
+  return <span>{icon?.getView() ?? props.value}</span>;
+}
+
+function cardRichContent(s: string) {
+  let result = s.match(iconRegexp);
+  if (result) {
+    const nodes: React.ReactNode[] = [];
+    let pos = 0;
+    for (const iconStr of result) {
+      const i = s.indexOf(iconStr, pos);
+      if (i >= 0) {
+        nodes.push(s.slice(pos, i));
+        nodes.push(<IconSpan key={i} value={iconStr} />);
+        pos = i + iconStr.length;
+      }
+    }
+    nodes.push(s.slice(pos));
+    return nodes;
+  }
+  return s;
+}
+
+type Range = {
+  from: number;
+  to: number;
+};
+
+function IconCodeEditor(props: {
+  codeControl: InstanceType<typeof StringControl>;
+  params: ControlParams;
+}) {
+  const [visible, setVisible] = useState(false);
+  const [range, setRange] = useState<Range>();
+  const widgetPopup = useCallback(
+    (v: EditorView) => (
+      <IconSelectBase
+        onChange={(value) => {
+          const r: Range = range ?? v.state.selection.ranges[0] ?? { from: 0, to: 0 };
+          const insert = '"' + value + '"';
+          setRange({ ...r, to: r.from + insert.length });
+          v.dispatch({ changes: { ...r, insert } });
+        }}
+        visible={visible}
+        setVisible={setVisible}
+        trigger="contextMenu"
+        // parent={document.querySelector<HTMLElement>(`${CodeEditorTooltipContainer}`)}
+        searchKeywords={i18nObjs.iconSearchKeywords}
+      />
+    ),
+    [visible, range]
+  );
+  const onClick = useCallback((e: React.MouseEvent, v: EditorView) => {
+    const r = onClickIcon(e, v);
+    if (r) {
+      setVisible(true);
+      setRange(r);
+    }
+  }, []);
+  const extraOnChange = useCallback((state: EditorState) => {
+    // popover should hide on change
+    setVisible(false);
+    setRange(undefined);
+  }, []);
+  return props.codeControl.codeEditor({
+    ...props.params,
+    enableIcon: true,
+    widgetPopup,
+    onClick,
+    extraOnChange,
+    cardRichContent,
+    cardTips: (
+      <>
+        {trans("iconControl.insertImage")}
+        <TacoButton style={{ display: "inline" }} onClick={() => setVisible(true)}>
+          {trans("iconControl.insertIcon")}
+        </TacoButton>
+      </>
+    ),
+  });
+}
+
+function isSelectValue(value: any) {
+  return !value || (typeof value === "string" && value.startsWith(iconPrefix));
+}
+
+type ChangeModeAction = {
+  useCodeEditor: boolean;
+};
+
+export function IconControlView(props: { value: string }) {
+  const { value } = props;
+  const icon = useIcon(value);
+  console.log(icon);
+  if (icon) {
+    return icon.getView();
+  }
+  return <StyledImage src={value} alt="" />;
+}
+
+export function IconscoutControl(
+  assetType: string = IconScoutAssetType.ICON,
+) {
+  return class extends AbstractComp<ReactNode, string, Node<ValueAndMsg<string>>> {
+    private readonly useCodeEditor: boolean;
+    private readonly codeControl: InstanceType<typeof StringControl>;
+
+    constructor(params: CompParams<string>) {
+      super(params);
+      this.useCodeEditor = !isSelectValue(params.value);
+      this.codeControl = new StringControl(params);
+    }
+
+    override getView(): ReactNode {
+      const value = this.codeControl.getView();
+      return <IconControlView value={value} />;
+    }
+
+    override getPropertyView(): ReactNode {
+      throw new Error("Method not implemented.");
+    }
+
+    changeModeAction() {
+      return customAction<ChangeModeAction>({ useCodeEditor: !this.useCodeEditor }, true);
+    }
+
+    propertyView(params: ControlParams) {
+      return wrapperToControlItem(
+        <ControlPropertyViewWrapper {...params}>
+          <IconPicker
+            assetType={assetType}
+            value={this.codeControl.getView()}
+            onChange={(x) => this.dispatchChangeValueAction(x)}
+            label={params.label}
+            IconType={params.IconType}
+          />
+        </ControlPropertyViewWrapper>
+      );
+    }
+
+    readonly IGNORABLE_DEFAULT_VALUE = "";
+    override toJsonValue(): string {
+      if (this.useCodeEditor) {
+        return this.codeControl.toJsonValue();
+      }
+      // in select mode, don't save editor's original value when saving
+      const v = removeQuote(this.codeControl.getView());
+      return isSelectValue(v) ? v : "";
+    }
+
+    override reduce(action: CompAction): this {
+      switch (action.type) {
+        case CompActionTypes.CUSTOM: {
+          const useCodeEditor = (action.value as ChangeModeAction).useCodeEditor;
+          let codeControl = this.codeControl;
+          if (!this.useCodeEditor && useCodeEditor) {
+            // value should be transformed when switching to editor from select mode
+            const value = this.codeControl.toJsonValue();
+            if (value && isSelectValue(value)) {
+              codeControl = codeControl.reduce(codeControl.changeValueAction(`{{ "${value}" }}`));
+            }
+          }
+          return setFieldsNoTypeCheck(this, { useCodeEditor, codeControl });
+        }
+        case CompActionTypes.CHANGE_VALUE: {
+          const useCodeEditor = this.useCodeEditor ? true : !isSelectValue(action.value);
+          const codeControl = this.codeControl.reduce(action);
+          if (useCodeEditor !== this.useCodeEditor || codeControl !== this.codeControl) {
+            return setFieldsNoTypeCheck(this, { useCodeEditor, codeControl });
+          }
+          return this;
+        }
+      }
+      const codeControl = this.codeControl.reduce(action);
+      if (codeControl !== this.codeControl) {
+        return setFieldsNoTypeCheck(this, { codeControl });
+      }
+      return this;
+    }
+
+    override nodeWithoutCache() {
+      return this.codeControl.nodeWithoutCache();
+    }
+
+    exposingNode() {
+      return this.codeControl.exposingNode();
+    }
+
+    override changeDispatch(dispatch: DispatchType): this {
+      const result = setFieldsNoTypeCheck(
+        super.changeDispatch(dispatch),
+        { codeControl: this.codeControl.changeDispatch(dispatch) },
+        { keepCacheKeys: ["node"] }
+      );
+      return result;
+    }
+  }
+}
+
+// export class IconscoutControl extends SimpleComp<string> {
+//   readonly IGNORABLE_DEFAULT_VALUE = false;
+//   protected getDefaultValue(): string {
+//     return '';
+//   }
+
+//   override getPropertyView(): ReactNode {
+//     throw new Error("Method not implemented.");
+//   }
+
+//   propertyView(params: ControlParams & { type?: "switch" | "checkbox" }) {
+//     return wrapperToControlItem(
+//       <ControlPropertyViewWrapper {...params}>
+//         <IconPicker
+//           value={this.value}
+//           onChange={(x) => this.dispatchChangeValueAction(x)}
+//           label={params.label}
+//           IconType={params.IconType}
+//         />
+//       </ControlPropertyViewWrapper>
+//     );
+//   }
+// }

From 6879f3b16f378efa36ae3afe2bd4240f2ee1e3ed Mon Sep 17 00:00:00 2001
From: RAHEEL <mraheeliftikhar1994@gmail.com>
Date: Wed, 3 Jul 2024 12:36:02 +0500
Subject: [PATCH 02/18] integrate iconscout with jsonLottie comp

---
 .../packages/lowcoder/src/api/iconscoutApi.ts |   9 +-
 .../lowcoder/src/comps/comps/imageComp.tsx    |   4 +-
 .../comps/comps/jsonComp/jsonLottieComp.tsx   |  44 ++-
 .../comps/comps/meetingComp/controlButton.tsx |   2 +-
 .../src/comps/controls/iconscoutControl.tsx   | 275 +++---------------
 5 files changed, 84 insertions(+), 250 deletions(-)

diff --git a/client/packages/lowcoder/src/api/iconscoutApi.ts b/client/packages/lowcoder/src/api/iconscoutApi.ts
index 8f947d88fb..a41c213e66 100644
--- a/client/packages/lowcoder/src/api/iconscoutApi.ts
+++ b/client/packages/lowcoder/src/api/iconscoutApi.ts
@@ -17,8 +17,8 @@ export type ResponseType = {
 };
 
 const apiUrl = "https://api.iconscout.com";
-const clientID = ""; //"91870410585071";
-const clientSecret = ""; // "GV5aCWpwdLWTxVXFBjMKSoyDPUyjzXLR";
+const clientID = "";
+const clientSecret = "";
 const currentPage = 1;
 const currentQuery = '';
 const currentData = [];
@@ -75,6 +75,11 @@ class IconscoutApi extends Api {
     });
     return response?.data.response.download;
   }
+
+  static async downloadJSON(url: string): Promise<any> {
+    const response = await axios.get(url)
+    return response?.data;
+  }
 }
 
 export default IconscoutApi;
diff --git a/client/packages/lowcoder/src/comps/comps/imageComp.tsx b/client/packages/lowcoder/src/comps/comps/imageComp.tsx
index 7456fc2ff5..ccd330a365 100644
--- a/client/packages/lowcoder/src/comps/comps/imageComp.tsx
+++ b/client/packages/lowcoder/src/comps/comps/imageComp.tsx
@@ -202,7 +202,7 @@ const ContainerImg = (props: RecordConstructorToView<typeof childrenMap>) => {
             <AntImage
               src={
                 props.sourceMode === 'advanced'
-                ? (props.srcIconScout as ReactElement)?.props.value
+                ? props.srcIconScout?.value
                 : props.src.value
               }
               referrerPolicy="same-origin"
@@ -253,7 +253,7 @@ let ImageBasicComp = new UICompBuilder(childrenMap, (props) => {
           {children.sourceMode.getView() === 'standard' && children.src.propertyView({
             label: trans("image.src"),
           })}
-          {children.sourceMode.getView() === 'advanced' &&children.srcIconScout.propertyView({
+          {children.sourceMode.getView() === 'advanced' && children.srcIconScout.propertyView({
             label: trans("image.src"),
           })}
         </Section>
diff --git a/client/packages/lowcoder/src/comps/comps/jsonComp/jsonLottieComp.tsx b/client/packages/lowcoder/src/comps/comps/jsonComp/jsonLottieComp.tsx
index cfaf06bca9..e1c664b1a0 100644
--- a/client/packages/lowcoder/src/comps/comps/jsonComp/jsonLottieComp.tsx
+++ b/client/packages/lowcoder/src/comps/comps/jsonComp/jsonLottieComp.tsx
@@ -2,6 +2,7 @@ import { hiddenPropertyView, showDataLoadingIndicatorsPropertyView } from "comps
 import {
   ArrayOrJSONObjectControl,
   NumberControl,
+  StringControl,
 } from "comps/controls/codeControl";
 import { dropdownControl } from "comps/controls/dropdownControl";
 import { BoolControl } from "comps/controls/boolControl";
@@ -19,6 +20,9 @@ import {
 import { defaultLottie } from "./jsonConstants";
 import { EditorContext } from "comps/editorState";
 import { IconScoutAssetType, IconscoutControl } from "@lowcoder-ee/comps/controls/iconscoutControl";
+import { isEmpty } from "lodash";
+import IconscoutApi from "@lowcoder-ee/api/iconscoutApi";
+import { changeValueAction, multiChangeAction } from "lowcoder-core";
 
 const Player = lazy(
   () => import('@lottiefiles/react-lottie-player')
@@ -98,7 +102,8 @@ let JsonLottieTmpComp = (function () {
       JSON.stringify(defaultLottie, null, 2)
     ),
     srcIconScout: IconscoutControl(IconScoutAssetType.LOTTIE),
-    valueIconScout: ArrayOrJSONObjectControl,
+    uuidIconScout: StringControl,
+    valueIconScout: withDefault(ArrayOrJSONObjectControl, JSON.stringify({})),
     speed: dropdownControl(speedOptions, "1"),
     width: withDefault(NumberControl, 100),
     height: withDefault(NumberControl, 100),
@@ -108,11 +113,38 @@ let JsonLottieTmpComp = (function () {
     loop: dropdownControl(loopOptions, "single"),
     keepLastFrame: BoolControl.DEFAULT_TRUE,
   };
-  return new UICompBuilder(childrenMap, (props) => {
-    console.log(props.srcIconScout);
+  return new UICompBuilder(childrenMap, (props, dispatch) => {
+
+    const downloadAsset = async (uuid: string) => {
+      try {
+        const result = await IconscoutApi.download(uuid, {
+          format: 'ai',
+        });
+        if (result && result.download_url) {
+          const json = await IconscoutApi.downloadJSON(result.download_url);
+          dispatch(
+            multiChangeAction({
+              uuidIconScout: changeValueAction(uuid, true),
+              valueIconScout: changeValueAction(JSON.stringify(json, null, 2), true)
+            })
+          ) 
+        }
+      } catch(error) {
+        console.error(error);
+      }
+
+    }
+    useEffect(() => {
+      if(props.srcIconScout?.uuid && props.srcIconScout?.uuid !== props.uuidIconScout) {
+        // get asset download link
+        downloadAsset(props.srcIconScout?.uuid);
+      }
+    }, [props.srcIconScout]);
+
     return (
       <div
         style={{
+          height: '100%',
           padding: `${props.container.margin}`,
           animation: props.animationStyle.animation,
           animationDelay: props.animationStyle.animationDelay,
@@ -139,7 +171,11 @@ let JsonLottieTmpComp = (function () {
             hover={props.animationStart === "on hover" && true}
             loop={props.loop === "single" ? false : true}
             speed={Number(props.speed)}
-            src={props.value}
+            src={
+              props.sourceMode === 'advanced'
+              ? (isEmpty(props.valueIconScout) ? '' : props.valueIconScout)
+              : props.value
+            }
             style={{
               height: "100%",
               width: "100%",
diff --git a/client/packages/lowcoder/src/comps/comps/meetingComp/controlButton.tsx b/client/packages/lowcoder/src/comps/comps/meetingComp/controlButton.tsx
index f8edb43d61..2239f3a39b 100644
--- a/client/packages/lowcoder/src/comps/comps/meetingComp/controlButton.tsx
+++ b/client/packages/lowcoder/src/comps/comps/meetingComp/controlButton.tsx
@@ -296,7 +296,7 @@ let ButtonTmpComp = (function () {
                       <IconScoutWrapper
                         $style={{ ...props.style, size: props.iconSize }}
                       >
-                        {props.prefixIconScout}
+                        <img src={props.prefixIconScout.value} />
                       </IconScoutWrapper>
                     )}
                   </Button100>
diff --git a/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx b/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
index 47cca644ff..b9c731903d 100644
--- a/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
+++ b/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
@@ -1,38 +1,18 @@
-import type { EditorState, EditorView } from "base/codeEditor/codeMirror";
-import { iconRegexp, iconWidgetClass } from "base/codeEditor/extensions/iconExtension";
-import { i18nObjs, trans } from "i18n";
+import { trans } from "i18n";
 import {
-  AbstractComp,
-  CompAction,
-  CompActionTypes,
-  CompParams,
-  customAction,
-  DispatchType,
-  Node,
   SimpleComp,
-  ValueAndMsg,
 } from "lowcoder-core";
 import {
   BlockGrayLabel,
-  controlItem,
   ControlPropertyViewWrapper,
   DeleteInputIcon,
-  iconPrefix,
-  IconSelect,
-  IconSelectBase,
-  removeQuote,
-  SwitchJsIcon,
-  SwitchWrapper,
   TacoButton,
   TacoInput,
   useIcon,
   wrapperToControlItem,
 } from "lowcoder-design";
-import { ReactNode, SetStateAction, useCallback, useEffect, useRef, useState } from "react";
+import { ReactNode, useCallback, useRef, useState } from "react";
 import styled from "styled-components";
-import { setFieldsNoTypeCheck } from "util/objectUtils";
-import { StringControl } from "./codeControl";
-import { ControlParams } from "./controlParams";
 import Popover from "antd/es/popover";
 import { CloseIcon, SearchIcon } from "icons";
 import Draggable from "react-draggable";
@@ -40,6 +20,7 @@ import IconscoutApi, { SearchParams } from "api/iconscoutApi";
 import List, { ListRowProps } from "react-virtualized/dist/es/List";
 import { debounce } from "lodash";
 import Spin from "antd/es/spin";
+import { ControlParams } from "./controlParams";
 
 const ButtonWrapper = styled.div`
   width: 100%;
@@ -215,8 +196,9 @@ const columnNum = 8;
 
 export const IconPicker = (props: {
   assetType: string;
+  uuid: string;
   value: string;
-  onChange: (value: string) => void;
+  onChange: (key: string, value: string) => void;
   label?: ReactNode;
   IconType?: "OnlyAntd" | "All" | "default" | undefined;
 }) => {
@@ -229,8 +211,8 @@ export const IconPicker = (props: {
   const onChangeRef = useRef(props.onChange);
   onChangeRef.current = props.onChange;
   const onChangeIcon = useCallback(
-    (key: string) => {
-      onChangeRef.current(key);
+    (key: string, value: string) => {
+      onChangeRef.current(key, value);
       setVisible(false);
     }, []
   );
@@ -250,11 +232,9 @@ export const IconPicker = (props: {
   const fetchAsset = async (uuid: string) => {
     try {
       const result = await IconscoutApi.download(uuid, {
-        format: props.assetType === IconScoutAssetType.LOTTIE ? 'ai' : 'svg',
+        format: 'svg',
       });
-      if (props.assetType !== IconScoutAssetType.LOTTIE) {
-        onChangeIcon(result.url);
-      }
+      onChangeIcon(result.uuid, result.url);
     } catch (error) {
       console.error(error);
     }
@@ -276,9 +256,10 @@ export const IconPicker = (props: {
               tabIndex={0}
               onClick={() => {
                 if (props.assetType === IconScoutAssetType.LOTTIE) {
-                  onChangeIcon(icon.urls.thumb)
+                  onChangeIcon(icon.uuid, icon.urls.thumb )
+                } else {
+                  fetchAsset(icon.uuid);
                 }
-                fetchAsset(icon.uuid);
               }}
             >
               <IconWrapper>
@@ -352,15 +333,15 @@ export const IconPicker = (props: {
           <ButtonWrapper>
             <ButtonIconWrapper>
               {props.assetType === IconScoutAssetType.LOTTIE && (
-                <>{props.value}</>
+                <video style={{'width': '100%'}} src={props.value} autoPlay />
               )}
               {props.assetType !== IconScoutAssetType.LOTTIE && (
-                <IconControlView value={props.value} />
+                <IconControlView value={props.value} uuid={props.uuid}/>
               )}
             </ButtonIconWrapper>
             <StyledDeleteInputIcon
               onClick={(e) => {
-                props.onChange("");
+                props.onChange("", "");
                 e.stopPropagation();
               }}
             />
@@ -373,239 +354,51 @@ export const IconPicker = (props: {
   );
 };
 
-function onClickIcon(e: React.MouseEvent, v: EditorView) {
-  for (let t = e.target as HTMLElement | null; t; t = t.parentElement) {
-    if (t.classList.contains(iconWidgetClass)) {
-      const pos = v.posAtDOM(t);
-      const result = iconRegexp.exec(v.state.doc.sliceString(pos));
-      if (result) {
-        const from = pos + result.index;
-        return { from, to: from + result[0].length };
-      }
-    }
-  }
-}
-
-function IconSpan(props: { value: string }) {
-  const icon = useIcon(props.value);
-  return <span>{icon?.getView() ?? props.value}</span>;
-}
-
-function cardRichContent(s: string) {
-  let result = s.match(iconRegexp);
-  if (result) {
-    const nodes: React.ReactNode[] = [];
-    let pos = 0;
-    for (const iconStr of result) {
-      const i = s.indexOf(iconStr, pos);
-      if (i >= 0) {
-        nodes.push(s.slice(pos, i));
-        nodes.push(<IconSpan key={i} value={iconStr} />);
-        pos = i + iconStr.length;
-      }
-    }
-    nodes.push(s.slice(pos));
-    return nodes;
-  }
-  return s;
-}
-
-type Range = {
-  from: number;
-  to: number;
-};
-
-function IconCodeEditor(props: {
-  codeControl: InstanceType<typeof StringControl>;
-  params: ControlParams;
-}) {
-  const [visible, setVisible] = useState(false);
-  const [range, setRange] = useState<Range>();
-  const widgetPopup = useCallback(
-    (v: EditorView) => (
-      <IconSelectBase
-        onChange={(value) => {
-          const r: Range = range ?? v.state.selection.ranges[0] ?? { from: 0, to: 0 };
-          const insert = '"' + value + '"';
-          setRange({ ...r, to: r.from + insert.length });
-          v.dispatch({ changes: { ...r, insert } });
-        }}
-        visible={visible}
-        setVisible={setVisible}
-        trigger="contextMenu"
-        // parent={document.querySelector<HTMLElement>(`${CodeEditorTooltipContainer}`)}
-        searchKeywords={i18nObjs.iconSearchKeywords}
-      />
-    ),
-    [visible, range]
-  );
-  const onClick = useCallback((e: React.MouseEvent, v: EditorView) => {
-    const r = onClickIcon(e, v);
-    if (r) {
-      setVisible(true);
-      setRange(r);
-    }
-  }, []);
-  const extraOnChange = useCallback((state: EditorState) => {
-    // popover should hide on change
-    setVisible(false);
-    setRange(undefined);
-  }, []);
-  return props.codeControl.codeEditor({
-    ...props.params,
-    enableIcon: true,
-    widgetPopup,
-    onClick,
-    extraOnChange,
-    cardRichContent,
-    cardTips: (
-      <>
-        {trans("iconControl.insertImage")}
-        <TacoButton style={{ display: "inline" }} onClick={() => setVisible(true)}>
-          {trans("iconControl.insertIcon")}
-        </TacoButton>
-      </>
-    ),
-  });
-}
-
-function isSelectValue(value: any) {
-  return !value || (typeof value === "string" && value.startsWith(iconPrefix));
-}
-
-type ChangeModeAction = {
-  useCodeEditor: boolean;
-};
-
-export function IconControlView(props: { value: string }) {
+export function IconControlView(props: { value: string, uuid: string }) {
   const { value } = props;
   const icon = useIcon(value);
-  console.log(icon);
+
   if (icon) {
     return icon.getView();
   }
   return <StyledImage src={value} alt="" />;
 }
 
+type DataType = {
+  uuid: string;
+  value: string;
+}
 export function IconscoutControl(
   assetType: string = IconScoutAssetType.ICON,
 ) {
-  return class extends AbstractComp<ReactNode, string, Node<ValueAndMsg<string>>> {
-    private readonly useCodeEditor: boolean;
-    private readonly codeControl: InstanceType<typeof StringControl>;
-
-    constructor(params: CompParams<string>) {
-      super(params);
-      this.useCodeEditor = !isSelectValue(params.value);
-      this.codeControl = new StringControl(params);
-    }
-
-    override getView(): ReactNode {
-      const value = this.codeControl.getView();
-      return <IconControlView value={value} />;
+  return class IconscoutControl extends SimpleComp<DataType> {
+    readonly IGNORABLE_DEFAULT_VALUE = false;
+    protected getDefaultValue(): DataType {
+      return {
+        uuid: '',
+        value: '',
+      };
     }
 
     override getPropertyView(): ReactNode {
       throw new Error("Method not implemented.");
     }
 
-    changeModeAction() {
-      return customAction<ChangeModeAction>({ useCodeEditor: !this.useCodeEditor }, true);
-    }
-
-    propertyView(params: ControlParams) {
+    propertyView(params: ControlParams & { type?: "switch" | "checkbox" }) {
       return wrapperToControlItem(
         <ControlPropertyViewWrapper {...params}>
           <IconPicker
             assetType={assetType}
-            value={this.codeControl.getView()}
-            onChange={(x) => this.dispatchChangeValueAction(x)}
+            uuid={this.value.uuid}
+            value={this.value.value}
+            onChange={(uuid, value) => {
+              this.dispatchChangeValueAction({uuid, value})
+            }}
             label={params.label}
             IconType={params.IconType}
           />
         </ControlPropertyViewWrapper>
       );
     }
-
-    readonly IGNORABLE_DEFAULT_VALUE = "";
-    override toJsonValue(): string {
-      if (this.useCodeEditor) {
-        return this.codeControl.toJsonValue();
-      }
-      // in select mode, don't save editor's original value when saving
-      const v = removeQuote(this.codeControl.getView());
-      return isSelectValue(v) ? v : "";
-    }
-
-    override reduce(action: CompAction): this {
-      switch (action.type) {
-        case CompActionTypes.CUSTOM: {
-          const useCodeEditor = (action.value as ChangeModeAction).useCodeEditor;
-          let codeControl = this.codeControl;
-          if (!this.useCodeEditor && useCodeEditor) {
-            // value should be transformed when switching to editor from select mode
-            const value = this.codeControl.toJsonValue();
-            if (value && isSelectValue(value)) {
-              codeControl = codeControl.reduce(codeControl.changeValueAction(`{{ "${value}" }}`));
-            }
-          }
-          return setFieldsNoTypeCheck(this, { useCodeEditor, codeControl });
-        }
-        case CompActionTypes.CHANGE_VALUE: {
-          const useCodeEditor = this.useCodeEditor ? true : !isSelectValue(action.value);
-          const codeControl = this.codeControl.reduce(action);
-          if (useCodeEditor !== this.useCodeEditor || codeControl !== this.codeControl) {
-            return setFieldsNoTypeCheck(this, { useCodeEditor, codeControl });
-          }
-          return this;
-        }
-      }
-      const codeControl = this.codeControl.reduce(action);
-      if (codeControl !== this.codeControl) {
-        return setFieldsNoTypeCheck(this, { codeControl });
-      }
-      return this;
-    }
-
-    override nodeWithoutCache() {
-      return this.codeControl.nodeWithoutCache();
-    }
-
-    exposingNode() {
-      return this.codeControl.exposingNode();
-    }
-
-    override changeDispatch(dispatch: DispatchType): this {
-      const result = setFieldsNoTypeCheck(
-        super.changeDispatch(dispatch),
-        { codeControl: this.codeControl.changeDispatch(dispatch) },
-        { keepCacheKeys: ["node"] }
-      );
-      return result;
-    }
   }
 }
-
-// export class IconscoutControl extends SimpleComp<string> {
-//   readonly IGNORABLE_DEFAULT_VALUE = false;
-//   protected getDefaultValue(): string {
-//     return '';
-//   }
-
-//   override getPropertyView(): ReactNode {
-//     throw new Error("Method not implemented.");
-//   }
-
-//   propertyView(params: ControlParams & { type?: "switch" | "checkbox" }) {
-//     return wrapperToControlItem(
-//       <ControlPropertyViewWrapper {...params}>
-//         <IconPicker
-//           value={this.value}
-//           onChange={(x) => this.dispatchChangeValueAction(x)}
-//           label={params.label}
-//           IconType={params.IconType}
-//         />
-//       </ControlPropertyViewWrapper>
-//     );
-//   }
-// }

From 65111f9d0c67ea807f9f62a7d39b2d894694b575 Mon Sep 17 00:00:00 2001
From: RAHEEL <mraheeliftikhar1994@gmail.com>
Date: Fri, 21 Feb 2025 00:08:56 +0500
Subject: [PATCH 03/18] added dotlottie option in lottie comp

---
 client/packages/lowcoder/package.json         |  1 +
 .../comps/comps/jsonComp/jsonLottieComp.tsx   | 79 +++++++++++++------
 client/yarn.lock                              | 19 +++++
 3 files changed, 76 insertions(+), 23 deletions(-)

diff --git a/client/packages/lowcoder/package.json b/client/packages/lowcoder/package.json
index 9338fa428d..4c4689031f 100644
--- a/client/packages/lowcoder/package.json
+++ b/client/packages/lowcoder/package.json
@@ -24,6 +24,7 @@
     "@fortawesome/free-regular-svg-icons": "^6.5.1",
     "@fortawesome/free-solid-svg-icons": "^6.5.1",
     "@fortawesome/react-fontawesome": "latest",
+    "@lottiefiles/dotlottie-react": "^0.13.0",
     "@manaflair/redux-batch": "^1.0.0",
     "@rjsf/antd": "^5.21.2",
     "@rjsf/core": "^5.21.2",
diff --git a/client/packages/lowcoder/src/comps/comps/jsonComp/jsonLottieComp.tsx b/client/packages/lowcoder/src/comps/comps/jsonComp/jsonLottieComp.tsx
index e1c664b1a0..4a32deb2c2 100644
--- a/client/packages/lowcoder/src/comps/comps/jsonComp/jsonLottieComp.tsx
+++ b/client/packages/lowcoder/src/comps/comps/jsonComp/jsonLottieComp.tsx
@@ -29,6 +29,11 @@ const Player = lazy(
     .then(module => ({default: module.Player}))
 );
 
+const DotLottiePlayer = lazy(
+  () => import('@lottiefiles/dotlottie-react')
+    .then(module => ({default: module.DotLottieReact}))
+);
+
 /**
  * JsonLottie Comp
  */
@@ -90,8 +95,9 @@ const speedOptions = [
 ] as const;
 
 const ModeOptions = [
-  { label: "Data", value: "standard" },
-  { label: "Advanced", value: "advanced" },
+  { label: "Lottie JSON", value: "standard" },
+  { label: "DotLottie", value: "dotLottie" },
+  { label: "IconScout", value: "advanced" },
 ] as const;
 
 let JsonLottieTmpComp = (function () {
@@ -102,6 +108,7 @@ let JsonLottieTmpComp = (function () {
       JSON.stringify(defaultLottie, null, 2)
     ),
     srcIconScout: IconscoutControl(IconScoutAssetType.LOTTIE),
+    srcDotLottie: withDefault(StringControl, 'https://assets-v2.lottiefiles.com/a/9e7d8a50-1180-11ee-89a6-3b0ab1ca8a0e/hUfEwc6xNt.lottie'),
     uuidIconScout: StringControl,
     valueIconScout: withDefault(ArrayOrJSONObjectControl, JSON.stringify({})),
     speed: dropdownControl(speedOptions, "1"),
@@ -162,27 +169,50 @@ let JsonLottieTmpComp = (function () {
             rotate: props.container.rotation,
           }}
         >
-          <Player
-            key={
-              [props.speed, props.animationStart, props.loop, props.value, props.keepLastFrame] as any
-            }
-            keepLastFrame={props.keepLastFrame}
-            autoplay={props.animationStart === "auto" && true}
-            hover={props.animationStart === "on hover" && true}
-            loop={props.loop === "single" ? false : true}
-            speed={Number(props.speed)}
-            src={
-              props.sourceMode === 'advanced'
-              ? (isEmpty(props.valueIconScout) ? '' : props.valueIconScout)
-              : props.value
-            }
-            style={{
-              height: "100%",
-              width: "100%",
-              maxWidth: "100%",
-              maxHeight: "100%",
-            }}
-          />
+          {props.sourceMode === 'dotLottie'
+            ? (
+              <DotLottiePlayer
+                key={
+                  [props.speed, props.animationStart, props.loop, props.value, props.keepLastFrame] as any
+                }
+                // keepLastFrame={props.keepLastFrame}
+                autoplay={props.animationStart === "auto" && true}
+                playOnHover={props.animationStart === "on hover" && true}
+                loop={props.loop === "single" ? false : true}
+                speed={Number(props.speed)}
+                src={props.srcDotLottie}
+                style={{
+                  height: "100%",
+                  width: "100%",
+                  maxWidth: "100%",
+                  maxHeight: "100%",
+                }}
+              />
+            )
+            : (
+              <Player
+                key={
+                  [props.speed, props.animationStart, props.loop, props.value, props.keepLastFrame] as any
+                }
+                keepLastFrame={props.keepLastFrame}
+                autoplay={props.animationStart === "auto" && true}
+                hover={props.animationStart === "on hover" && true}
+                loop={props.loop === "single" ? false : true}
+                speed={Number(props.speed)}
+                src={
+                  props.sourceMode === 'advanced'
+                  ? (isEmpty(props.valueIconScout) ? '' : props.valueIconScout)
+                  : props.value
+                }
+                style={{
+                  height: "100%",
+                  width: "100%",
+                  maxWidth: "100%",
+                  maxHeight: "100%",
+                }}
+              />
+            )
+          }
         </div>
       </div>
     );
@@ -198,6 +228,9 @@ let JsonLottieTmpComp = (function () {
             {children.sourceMode.getView() === 'standard' && children.value.propertyView({
               label: trans("jsonLottie.lottieJson"),
             })}
+            {children.sourceMode.getView() === 'dotLottie' && children.srcDotLottie.propertyView({
+              label: "Source",
+            })}
             {children.sourceMode.getView() === 'advanced' && children.srcIconScout.propertyView({
               label: "Lottie Source",
             })}
diff --git a/client/yarn.lock b/client/yarn.lock
index 7fae135fa1..b819a27167 100644
--- a/client/yarn.lock
+++ b/client/yarn.lock
@@ -3113,6 +3113,24 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@lottiefiles/dotlottie-react@npm:^0.13.0":
+  version: 0.13.0
+  resolution: "@lottiefiles/dotlottie-react@npm:0.13.0"
+  dependencies:
+    "@lottiefiles/dotlottie-web": 0.40.1
+  peerDependencies:
+    react: ^17 || ^18 || ^19
+  checksum: bafe6ded727aab991ff03f6ff5a2fd1a41b1f429b36175f34140017fc684e0a8ef7f7b713d189bd49948c4b728fe1d05c7d8c20a0bea0d8c1ae1ed87614fe843
+  languageName: node
+  linkType: hard
+
+"@lottiefiles/dotlottie-web@npm:0.40.1":
+  version: 0.40.1
+  resolution: "@lottiefiles/dotlottie-web@npm:0.40.1"
+  checksum: a79e60c33845311cb055ea661abb2f4211063e149788aea724afbed05a09ae569d50b4c0e5825d13eb5fc62a33c3dc74f2f3900fdb1e99f8594feddc72d2cc27
+  languageName: node
+  linkType: hard
+
 "@lottiefiles/react-lottie-player@npm:^3.5.3":
   version: 3.5.3
   resolution: "@lottiefiles/react-lottie-player@npm:3.5.3"
@@ -14232,6 +14250,7 @@ coolshapes-react@lowcoder-org/coolshapes-react:
     "@fortawesome/free-regular-svg-icons": ^6.5.1
     "@fortawesome/free-solid-svg-icons": ^6.5.1
     "@fortawesome/react-fontawesome": latest
+    "@lottiefiles/dotlottie-react": ^0.13.0
     "@manaflair/redux-batch": ^1.0.0
     "@rjsf/antd": ^5.21.2
     "@rjsf/core": ^5.21.2

From 9dfe557a29a5389657ad02fc1baa9b8c370dfcbf Mon Sep 17 00:00:00 2001
From: RAHEEL <mraheeliftikhar1994@gmail.com>
Date: Mon, 24 Feb 2025 23:56:27 +0500
Subject: [PATCH 04/18] download asset from icon-scout and save as base64
 string

---
 .../packages/lowcoder/src/api/iconscoutApi.ts |   7 +-
 .../src/comps/controls/iconscoutControl.tsx   | 113 +++++++++++-------
 2 files changed, 73 insertions(+), 47 deletions(-)

diff --git a/client/packages/lowcoder/src/api/iconscoutApi.ts b/client/packages/lowcoder/src/api/iconscoutApi.ts
index a41c213e66..0d44a7ef23 100644
--- a/client/packages/lowcoder/src/api/iconscoutApi.ts
+++ b/client/packages/lowcoder/src/api/iconscoutApi.ts
@@ -68,16 +68,15 @@ class IconscoutApi extends Api {
 
   static async download(uuid: string, params: Record<string, string>): Promise<any> {
     const response = await getAxiosInstance(clientSecret).request({
-      url: `/v3/items/${uuid}/api-download`,
+      url: `/v3/items/${uuid}/api-download?format=${params.format}`,
       method: "POST",
       withCredentials: false,
-      params,
     });
     return response?.data.response.download;
   }
 
-  static async downloadJSON(url: string): Promise<any> {
-    const response = await axios.get(url)
+  static async downloadAsset(url: string): Promise<any> {
+    const response = await axios.get(url, {responseType: 'blob'})
     return response?.data;
   }
 }
diff --git a/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx b/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
index b9c731903d..7c356624a9 100644
--- a/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
+++ b/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
@@ -11,7 +11,7 @@ import {
   useIcon,
   wrapperToControlItem,
 } from "lowcoder-design";
-import { ReactNode, useCallback, useRef, useState } from "react";
+import { ReactNode, useCallback, useMemo, useRef, useState } from "react";
 import styled from "styled-components";
 import Popover from "antd/es/popover";
 import { CloseIcon, SearchIcon } from "icons";
@@ -21,6 +21,7 @@ import List, { ListRowProps } from "react-virtualized/dist/es/List";
 import { debounce } from "lodash";
 import Spin from "antd/es/spin";
 import { ControlParams } from "./controlParams";
+import { getBase64 } from "@lowcoder-ee/util/fileUtils";
 
 const ButtonWrapper = styled.div`
   width: 100%;
@@ -175,13 +176,19 @@ const IconKeyDisplay = styled.div`
   width: 100%; /* Ensure the container can grow */
 `;
 
-export enum IconScoutAssetType {
+export enum AssetType {
   ICON = "icon",
   ILLUSTRATION = "illustration",
   // '3D' = "3d",
   LOTTIE = "lottie",
 }
 
+export type IconScoutAsset = {
+  uuid: string;
+  value: string;
+  preview: string;
+}
+
 const IconScoutSearchParams: SearchParams = {
   query: '',
   product_type: 'item',
@@ -198,27 +205,26 @@ export const IconPicker = (props: {
   assetType: string;
   uuid: string;
   value: string;
-  onChange: (key: string, value: string) => void;
+  preview: string;
+  onChange: (key: string, value: string, preview: string) => void;
   label?: ReactNode;
   IconType?: "OnlyAntd" | "All" | "default" | undefined;
 }) => {
-  console.log(props.value, props.assetType);
-  const icon = useIcon(props.value);
   const [ visible, setVisible ] = useState(false)
   const [ loading, setLoading ] = useState(false)
-  const [searchText, setSearchText] = useState("");
   const [ searchResults, setSearchResults ] = useState<Array<any>>([]);
+
   const onChangeRef = useRef(props.onChange);
   onChangeRef.current = props.onChange;
+
   const onChangeIcon = useCallback(
-    (key: string, value: string) => {
-      onChangeRef.current(key, value);
+    (key: string, value: string, url: string) => {
+      onChangeRef.current(key, value, url);
       setVisible(false);
     }, []
   );
 
   const fetchResults = async (query: string) => {
-    console.log('query change', query);
     setLoading(true);
     const result = await IconscoutApi.search({
       ...IconScoutSearchParams,
@@ -229,19 +235,38 @@ export const IconPicker = (props: {
     setSearchResults(result.data);
   };
 
-  const fetchAsset = async (uuid: string) => {
+  const downloadAsset = async (
+    uuid: string,
+    downloadUrl: string,
+    callback: (assetUrl: string) => void,
+  ) => {
+    try {
+      if (uuid && downloadUrl) {
+        const json = await IconscoutApi.downloadAsset(downloadUrl);
+        getBase64(json, (url: string) => {
+          callback(url);
+        });
+      }
+    } catch(error) {
+      console.error(error);
+    }
+  }
+
+  const fetchDownloadUrl = async (uuid: string) => {
     try {
       const result = await IconscoutApi.download(uuid, {
-        format: 'svg',
+        format: props.assetType === AssetType.LOTTIE ? 'lottie' : 'svg',
+      });
+
+      downloadAsset(uuid, result.download_url, (assetUrl: string) => {
+        onChangeIcon(uuid, assetUrl, result.url);
       });
-      onChangeIcon(result.uuid, result.url);
     } catch (error) {
       console.error(error);
     }
   }
 
   const handleChange = debounce((e) => {
-    setSearchText(e.target.value);
     fetchResults(e.target.value);
   }, 500);
 
@@ -255,21 +280,17 @@ export const IconPicker = (props: {
               key={icon.uuid}
               tabIndex={0}
               onClick={() => {
-                if (props.assetType === IconScoutAssetType.LOTTIE) {
-                  onChangeIcon(icon.uuid, icon.urls.thumb )
-                } else {
-                  fetchAsset(icon.uuid);
-                }
+                fetchDownloadUrl(icon.uuid);
               }}
             >
               <IconWrapper>
-                {props.assetType === IconScoutAssetType.ICON && (
+                {props.assetType === AssetType.ICON && (
                   <img style={{'width': '100%'}} src={icon.urls.png_64} />
                 )}
-                {props.assetType === IconScoutAssetType.ILLUSTRATION && (
+                {props.assetType === AssetType.ILLUSTRATION && (
                   <img style={{'width': '100%'}} src={icon.urls.thumb} />
                 )}
-                {props.assetType === IconScoutAssetType.LOTTIE && (
+                {props.assetType === AssetType.LOTTIE && (
                   <video style={{'width': '100%'}} src={icon.urls.thumb} autoPlay />
                 )}
               </IconWrapper>
@@ -279,6 +300,12 @@ export const IconPicker = (props: {
     ),[searchResults]
   );
 
+  const popupTitle = useMemo(() => {
+    if (props.assetType === AssetType.ILLUSTRATION) return 'Search Image';
+    if (props.assetType === AssetType.LOTTIE) return 'Search Animation';
+    return 'Search Icon';
+  }, [props.assetType]);
+
   return (
     <Popover
       trigger={'click'}
@@ -288,25 +315,27 @@ export const IconPicker = (props: {
       onOpenChange={setVisible}
       // getPopupContainer={parent ? () => parent : undefined}
       // hide the original background when dragging the popover is allowed
-      overlayInnerStyle={{
-        border: "none",
-        boxShadow: "none",
-        background: "transparent",
-      }}
       // when dragging is allowed, always re-location to avoid the popover exceeds the screen
+      styles={{
+        body: {
+          border: "none",
+          boxShadow: "none",
+          background: "transparent",
+        }
+      }}
       destroyTooltipOnHide
       content={
         <Draggable handle=".dragHandle">
           <PopupContainer>
             <TitleDiv className="dragHandle">
-              <TitleText>{"Select Icon"}</TitleText>
+              <TitleText>{popupTitle}</TitleText>
               <StyledCloseIcon onClick={() => setVisible(false)} />
             </TitleDiv>
             <SearchDiv>
               <TacoInput
                 style={{ width: "100%", paddingLeft: "40px" }}
                 onChange={handleChange}
-                placeholder={"Search Icon"}
+                placeholder={popupTitle}
               />
               <StyledSearchIcon />
             </SearchDiv>
@@ -329,19 +358,19 @@ export const IconPicker = (props: {
       }
     >
       <TacoButton style={{ width: "100%" }}>
-        {props.value ? (
+        {props.preview ? (
           <ButtonWrapper>
             <ButtonIconWrapper>
-              {props.assetType === IconScoutAssetType.LOTTIE && (
-                <video style={{'width': '100%'}} src={props.value} autoPlay />
+              {props.assetType === AssetType.LOTTIE && (
+                <video style={{'width': '100%'}} src={props.preview} autoPlay />
               )}
-              {props.assetType !== IconScoutAssetType.LOTTIE && (
-                <IconControlView value={props.value} uuid={props.uuid}/>
+              {props.assetType !== AssetType.LOTTIE && (
+                <IconControlView value={props.preview} uuid={props.uuid}/>
               )}
             </ButtonIconWrapper>
             <StyledDeleteInputIcon
               onClick={(e) => {
-                props.onChange("", "");
+                props.onChange("", "", "");
                 e.stopPropagation();
               }}
             />
@@ -364,19 +393,16 @@ export function IconControlView(props: { value: string, uuid: string }) {
   return <StyledImage src={value} alt="" />;
 }
 
-type DataType = {
-  uuid: string;
-  value: string;
-}
 export function IconscoutControl(
-  assetType: string = IconScoutAssetType.ICON,
+  assetType: string = AssetType.ICON,
 ) {
-  return class IconscoutControl extends SimpleComp<DataType> {
+  return class IconscoutControl extends SimpleComp<IconScoutAsset> {
     readonly IGNORABLE_DEFAULT_VALUE = false;
-    protected getDefaultValue(): DataType {
+    protected getDefaultValue(): IconScoutAsset {
       return {
         uuid: '',
         value: '',
+        preview: '',
       };
     }
 
@@ -391,8 +417,9 @@ export function IconscoutControl(
             assetType={assetType}
             uuid={this.value.uuid}
             value={this.value.value}
-            onChange={(uuid, value) => {
-              this.dispatchChangeValueAction({uuid, value})
+            preview={this.value.preview}
+            onChange={(uuid, value, preview) => {
+              this.dispatchChangeValueAction({uuid, value, preview})
             }}
             label={params.label}
             IconType={params.IconType}

From 7ee413587594d0d714600689c59dbb1e468bc8a0 Mon Sep 17 00:00:00 2001
From: RAHEEL <mraheeliftikhar1994@gmail.com>
Date: Mon, 24 Feb 2025 23:57:32 +0500
Subject: [PATCH 05/18] updated naming for iconScoutAsset

---
 .../packages/lowcoder/src/comps/comps/imageComp.tsx  | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/client/packages/lowcoder/src/comps/comps/imageComp.tsx b/client/packages/lowcoder/src/comps/comps/imageComp.tsx
index ccd330a365..1806399e26 100644
--- a/client/packages/lowcoder/src/comps/comps/imageComp.tsx
+++ b/client/packages/lowcoder/src/comps/comps/imageComp.tsx
@@ -36,7 +36,7 @@ import { EditorContext } from "comps/editorState";
 import { StringControl } from "../controls/codeControl";
 import { PositionControl } from "comps/controls/dropdownControl";
 import { dropdownControl } from "../controls/dropdownControl";
-import { IconScoutAssetType, IconscoutControl } from "../controls/iconscoutControl";
+import { AssetType, IconscoutControl } from "../controls/iconscoutControl";
 
 const Container = styled.div<{ 
   $style: ImageStyleType | undefined, 
@@ -115,7 +115,7 @@ const getStyle = (style: ImageStyleType) => {
 const EventOptions = [clickEvent] as const;
 const ModeOptions = [
   { label: "URL", value: "standard" },
-  { label: "Advanced", value: "advanced" },
+  { label: "Asset Library", value: "asset-library" },
 ] as const;
 
 const ContainerImg = (props: RecordConstructorToView<typeof childrenMap>) => {
@@ -201,8 +201,8 @@ const ContainerImg = (props: RecordConstructorToView<typeof childrenMap>) => {
           >
             <AntImage
               src={
-                props.sourceMode === 'advanced'
-                ? props.srcIconScout?.value
+                props.sourceMode === 'asset-library'
+                ? props.iconScoutAsset?.value
                 : props.src.value
               }
               referrerPolicy="same-origin"
@@ -222,7 +222,7 @@ const ContainerImg = (props: RecordConstructorToView<typeof childrenMap>) => {
 const childrenMap = {
   sourceMode: dropdownControl(ModeOptions, "standard"),
   src: withDefault(StringStateControl, "https://temp.im/350x400"),
-  srcIconScout: IconscoutControl(IconScoutAssetType.ILLUSTRATION),
+  iconScoutAsset: IconscoutControl(AssetType.ILLUSTRATION),
   onEvent: eventHandlerControl(EventOptions),
   style: styleControl(ImageStyle , 'style'),
   animationStyle: styleControl(AnimationStyle , 'animationStyle'),
@@ -253,7 +253,7 @@ let ImageBasicComp = new UICompBuilder(childrenMap, (props) => {
           {children.sourceMode.getView() === 'standard' && children.src.propertyView({
             label: trans("image.src"),
           })}
-          {children.sourceMode.getView() === 'advanced' && children.srcIconScout.propertyView({
+          {children.sourceMode.getView() === 'asset-library' && children.iconScoutAsset.propertyView({
             label: trans("image.src"),
           })}
         </Section>

From fdc8050e8f026d0d819b226f11d2ec8a0967a103 Mon Sep 17 00:00:00 2001
From: RAHEEL <mraheeliftikhar1994@gmail.com>
Date: Mon, 24 Feb 2025 23:57:43 +0500
Subject: [PATCH 06/18] updated naming for iconScoutAsset

---
 .../src/comps/comps/meetingComp/controlButton.tsx  | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/client/packages/lowcoder/src/comps/comps/meetingComp/controlButton.tsx b/client/packages/lowcoder/src/comps/comps/meetingComp/controlButton.tsx
index 2239f3a39b..ec3f0a216a 100644
--- a/client/packages/lowcoder/src/comps/comps/meetingComp/controlButton.tsx
+++ b/client/packages/lowcoder/src/comps/comps/meetingComp/controlButton.tsx
@@ -39,7 +39,7 @@ import { useEffect, useRef, useState } from "react";
 import ReactResizeDetector from "react-resize-detector";
 
 import { useContext } from "react";
-import { IconScoutAssetType, IconscoutControl } from "@lowcoder-ee/comps/controls/iconscoutControl";
+import { AssetType, IconscoutControl } from "@lowcoder-ee/comps/controls/iconscoutControl";
 
 const Container = styled.div<{ $style: any }>`
   height: 100%;
@@ -173,7 +173,7 @@ const typeOptions = [
 
 const ModeOptions = [
   { label: "Standard", value: "standard" },
-  { label: "Advanced", value: "advanced" },
+  { label: "Asset Library", value: "asset-library" },
 ] as const;
 
 function isDefault(type?: string) {
@@ -198,7 +198,7 @@ const childrenMap = {
   form: SelectFormControl,
   sourceMode: dropdownControl(ModeOptions, "standard"),
   prefixIcon: IconControl,
-  prefixIconScout: IconscoutControl(IconScoutAssetType.ICON),
+  iconScoutAsset: IconscoutControl(AssetType.ICON),
   style: ButtonStyleControl,
   viewRef: RefControl<HTMLElement>,
   restrictPaddingOnRotation:withDefault(StringControl, 'controlButton')
@@ -241,7 +241,7 @@ let ButtonTmpComp = (function () {
 
       setStyle(container?.clientHeight + "px", container?.clientWidth + "px");
     };
-    console.log(props.prefixIconScout);
+
     return (
       <EditorContext.Consumer>
         {(editorState) => (
@@ -292,11 +292,11 @@ let ButtonTmpComp = (function () {
                         {props.prefixIcon}
                       </IconWrapper>
                     )}
-                    {props.sourceMode === 'advanced' && props.prefixIconScout && (
+                    {props.sourceMode === 'asset-library' && props.iconScoutAsset && (
                       <IconScoutWrapper
                         $style={{ ...props.style, size: props.iconSize }}
                       >
-                        <img src={props.prefixIconScout.value} />
+                        <img src={props.iconScoutAsset.value} />
                       </IconScoutWrapper>
                     )}
                   </Button100>
@@ -319,7 +319,7 @@ let ButtonTmpComp = (function () {
           {children.sourceMode.getView() === 'standard' && children.prefixIcon.propertyView({
             label: trans("button.icon"),
           })}
-          {children.sourceMode.getView() === 'advanced' &&children.prefixIconScout.propertyView({
+          {children.sourceMode.getView() === 'asset-library' &&children.iconScoutAsset.propertyView({
             label: trans("button.icon"),
           })}
         </Section>

From b29b1dd4288a15e21b25a1a94b263805aa3ce29f Mon Sep 17 00:00:00 2001
From: RAHEEL <mraheeliftikhar1994@gmail.com>
Date: Mon, 24 Feb 2025 23:58:37 +0500
Subject: [PATCH 07/18] used dotLottie player for different modes

---
 .../comps/comps/jsonComp/jsonLottieComp.tsx   | 135 ++++++------------
 1 file changed, 44 insertions(+), 91 deletions(-)

diff --git a/client/packages/lowcoder/src/comps/comps/jsonComp/jsonLottieComp.tsx b/client/packages/lowcoder/src/comps/comps/jsonComp/jsonLottieComp.tsx
index 4a32deb2c2..08d0b08fd3 100644
--- a/client/packages/lowcoder/src/comps/comps/jsonComp/jsonLottieComp.tsx
+++ b/client/packages/lowcoder/src/comps/comps/jsonComp/jsonLottieComp.tsx
@@ -2,7 +2,6 @@ import { hiddenPropertyView, showDataLoadingIndicatorsPropertyView } from "comps
 import {
   ArrayOrJSONObjectControl,
   NumberControl,
-  StringControl,
 } from "comps/controls/codeControl";
 import { dropdownControl } from "comps/controls/dropdownControl";
 import { BoolControl } from "comps/controls/boolControl";
@@ -10,7 +9,7 @@ import { styleControl } from "comps/controls/styleControl";
 import { AnimationStyle, LottieStyle } from "comps/controls/styleControlConstants";
 import { trans } from "i18n";
 import { Section, sectionNames } from "lowcoder-design";
-import { useContext, lazy, useEffect } from "react";  
+import { useContext, lazy, useEffect, useState } from "react";  
 import { UICompBuilder, withDefault } from "../../generators";
 import {
   NameConfig,
@@ -19,15 +18,13 @@ import {
 } from "../../generators/withExposing";
 import { defaultLottie } from "./jsonConstants";
 import { EditorContext } from "comps/editorState";
-import { IconScoutAssetType, IconscoutControl } from "@lowcoder-ee/comps/controls/iconscoutControl";
-import { isEmpty } from "lodash";
-import IconscoutApi from "@lowcoder-ee/api/iconscoutApi";
-import { changeValueAction, multiChangeAction } from "lowcoder-core";
+import { AssetType, IconscoutControl } from "@lowcoder-ee/comps/controls/iconscoutControl";
+import { DotLottie } from "@lottiefiles/dotlottie-react";
 
-const Player = lazy(
-  () => import('@lottiefiles/react-lottie-player')
-    .then(module => ({default: module.Player}))
-);
+// const Player = lazy(
+//   () => import('@lottiefiles/react-lottie-player')
+//     .then(module => ({default: module.Player}))
+// );
 
 const DotLottiePlayer = lazy(
   () => import('@lottiefiles/dotlottie-react')
@@ -44,7 +41,7 @@ const animationStartOptions = [
   },
   {
     label: trans("jsonLottie.onHover"),
-    value: "on hover",
+    value: "hover",
   },
 ] as const;
 
@@ -96,8 +93,7 @@ const speedOptions = [
 
 const ModeOptions = [
   { label: "Lottie JSON", value: "standard" },
-  { label: "DotLottie", value: "dotLottie" },
-  { label: "IconScout", value: "advanced" },
+  { label: "Asset Library", value: "asset-library" }
 ] as const;
 
 let JsonLottieTmpComp = (function () {
@@ -107,10 +103,7 @@ let JsonLottieTmpComp = (function () {
       ArrayOrJSONObjectControl,
       JSON.stringify(defaultLottie, null, 2)
     ),
-    srcIconScout: IconscoutControl(IconScoutAssetType.LOTTIE),
-    srcDotLottie: withDefault(StringControl, 'https://assets-v2.lottiefiles.com/a/9e7d8a50-1180-11ee-89a6-3b0ab1ca8a0e/hUfEwc6xNt.lottie'),
-    uuidIconScout: StringControl,
-    valueIconScout: withDefault(ArrayOrJSONObjectControl, JSON.stringify({})),
+    iconScoutAsset: IconscoutControl(AssetType.LOTTIE),
     speed: dropdownControl(speedOptions, "1"),
     width: withDefault(NumberControl, 100),
     height: withDefault(NumberControl, 100),
@@ -121,32 +114,23 @@ let JsonLottieTmpComp = (function () {
     keepLastFrame: BoolControl.DEFAULT_TRUE,
   };
   return new UICompBuilder(childrenMap, (props, dispatch) => {
-
-    const downloadAsset = async (uuid: string) => {
-      try {
-        const result = await IconscoutApi.download(uuid, {
-          format: 'ai',
-        });
-        if (result && result.download_url) {
-          const json = await IconscoutApi.downloadJSON(result.download_url);
-          dispatch(
-            multiChangeAction({
-              uuidIconScout: changeValueAction(uuid, true),
-              valueIconScout: changeValueAction(JSON.stringify(json, null, 2), true)
-            })
-          ) 
-        }
-      } catch(error) {
-        console.error(error);
+    const [dotLottie, setDotLottie] = useState<DotLottie | null>(null);
+    
+    useEffect(() => {
+      const onComplete = () => {
+        props.keepLastFrame && dotLottie?.setFrame(100);
       }
 
-    }
-    useEffect(() => {
-      if(props.srcIconScout?.uuid && props.srcIconScout?.uuid !== props.uuidIconScout) {
-        // get asset download link
-        downloadAsset(props.srcIconScout?.uuid);
+      if (dotLottie) {
+        dotLottie.addEventListener('complete', onComplete);
       }
-    }, [props.srcIconScout]);
+  
+      return () => {
+        if (dotLottie) {
+          dotLottie.removeEventListener('complete', onComplete);
+        }
+      };
+    }, [dotLottie, props.keepLastFrame]);
 
     return (
       <div
@@ -169,50 +153,25 @@ let JsonLottieTmpComp = (function () {
             rotate: props.container.rotation,
           }}
         >
-          {props.sourceMode === 'dotLottie'
-            ? (
-              <DotLottiePlayer
-                key={
-                  [props.speed, props.animationStart, props.loop, props.value, props.keepLastFrame] as any
-                }
-                // keepLastFrame={props.keepLastFrame}
-                autoplay={props.animationStart === "auto" && true}
-                playOnHover={props.animationStart === "on hover" && true}
-                loop={props.loop === "single" ? false : true}
-                speed={Number(props.speed)}
-                src={props.srcDotLottie}
-                style={{
-                  height: "100%",
-                  width: "100%",
-                  maxWidth: "100%",
-                  maxHeight: "100%",
-                }}
-              />
-            )
-            : (
-              <Player
-                key={
-                  [props.speed, props.animationStart, props.loop, props.value, props.keepLastFrame] as any
-                }
-                keepLastFrame={props.keepLastFrame}
-                autoplay={props.animationStart === "auto" && true}
-                hover={props.animationStart === "on hover" && true}
-                loop={props.loop === "single" ? false : true}
-                speed={Number(props.speed)}
-                src={
-                  props.sourceMode === 'advanced'
-                  ? (isEmpty(props.valueIconScout) ? '' : props.valueIconScout)
-                  : props.value
-                }
-                style={{
-                  height: "100%",
-                  width: "100%",
-                  maxWidth: "100%",
-                  maxHeight: "100%",
-                }}
-              />
-            )
-          }
+          <DotLottiePlayer
+            key={
+              [props.speed, props.animationStart, props.loop, props.value, props.keepLastFrame] as any
+            }
+            dotLottieRefCallback={setDotLottie}
+            autoplay={props.animationStart === "auto"}
+            loop={props.loop === "single" ? false : true}
+            speed={Number(props.speed)}
+            data={props.sourceMode === 'standard' ? props.value as Record<string, undefined> : undefined}
+            src={props.sourceMode === 'asset-library' ? props.iconScoutAsset?.value : undefined}
+            style={{
+              height: "100%",
+              width: "100%",
+              maxWidth: "100%",
+              maxHeight: "100%",
+            }}
+            onMouseEnter={() => props.animationStart === "hover" && dotLottie?.play()}
+            onMouseLeave={() => props.animationStart === "hover" && dotLottie?.pause()}
+          />
         </div>
       </div>
     );
@@ -228,15 +187,9 @@ let JsonLottieTmpComp = (function () {
             {children.sourceMode.getView() === 'standard' && children.value.propertyView({
               label: trans("jsonLottie.lottieJson"),
             })}
-            {children.sourceMode.getView() === 'dotLottie' && children.srcDotLottie.propertyView({
-              label: "Source",
-            })}
-            {children.sourceMode.getView() === 'advanced' && children.srcIconScout.propertyView({
+            {children.sourceMode.getView() === 'asset-library' && children.iconScoutAsset.propertyView({
               label: "Lottie Source",
             })}
-            {children.sourceMode.getView() === 'advanced' && children.valueIconScout.propertyView({
-              label: trans("jsonLottie.lottieJson"),
-            })}
           </Section>
 
           {(useContext(EditorContext).editorModeStatus === "logic" || useContext(EditorContext).editorModeStatus === "both") && (

From 09d7e9d389b2227899c0e93cb87148b6069de3bd Mon Sep 17 00:00:00 2001
From: RAHEEL <mraheeliftikhar1994@gmail.com>
Date: Mon, 24 Feb 2025 23:59:05 +0500
Subject: [PATCH 08/18] added option in IconComp to select icons from IconScout

---
 .../lowcoder/src/comps/comps/iconComp.tsx     | 24 ++++++++++++++++---
 1 file changed, 21 insertions(+), 3 deletions(-)

diff --git a/client/packages/lowcoder/src/comps/comps/iconComp.tsx b/client/packages/lowcoder/src/comps/comps/iconComp.tsx
index f93b0eb2a3..9a8eb17907 100644
--- a/client/packages/lowcoder/src/comps/comps/iconComp.tsx
+++ b/client/packages/lowcoder/src/comps/comps/iconComp.tsx
@@ -30,6 +30,8 @@ import {
 } from "../controls/eventHandlerControl";
 import { useContext } from "react";
 import { EditorContext } from "comps/editorState";
+import { AssetType, IconscoutControl } from "@lowcoder-ee/comps/controls/iconscoutControl";
+import { dropdownControl } from "../controls/dropdownControl";
 
 const Container = styled.div<{
   $style: IconStyleType | undefined;
@@ -61,10 +63,17 @@ const Container = styled.div<{
 
 const EventOptions = [clickEvent] as const;
 
+const ModeOptions = [
+  { label: "Standard", value: "standard" },
+  { label: "Asset Library", value: "asset-library" },
+] as const;
+
 const childrenMap = {
   style: styleControl(IconStyle,'style'),
   animationStyle: styleControl(AnimationStyle,'animationStyle'),
+  sourceMode: dropdownControl(ModeOptions, "standard"),
   icon: withDefault(IconControl, "/icon:antd/homefilled"),
+  iconScoutAsset: IconscoutControl(AssetType.ICON),
   autoHeight: withDefault(AutoHeightControl, "auto"),
   iconSize: withDefault(NumberControl, 20),
   onEvent: eventHandlerControl(EventOptions),
@@ -103,7 +112,10 @@ const IconView = (props: RecordConstructorToView<typeof childrenMap>) => {
           }}
           onClick={() => props.onEvent("click")}
         >
-          {props.icon}
+          { props.sourceMode === 'standard'
+            ? props.icon
+            : <img src={props.iconScoutAsset.value} />
+          }
         </Container>
       )}
     >
@@ -117,11 +129,17 @@ let IconBasicComp = (function () {
     .setPropertyViewFn((children) => (
       <>
         <Section name={sectionNames.basic}>
-          {children.icon.propertyView({
+          { children.sourceMode.propertyView({
+            label: "",
+            radioButton: true
+          })}
+          {children.sourceMode.getView() === 'standard' && children.icon.propertyView({
             label: trans("iconComp.icon"),
             IconType: "All",
           })}
-          
+          {children.sourceMode.getView() === 'asset-library' && children.iconScoutAsset.propertyView({
+            label: trans("button.icon"),
+          })}
         </Section> 
 
         {["logic", "both"].includes(useContext(EditorContext).editorModeStatus) && (

From 60e456181d08306b341ef796148dfc287e35bf14 Mon Sep 17 00:00:00 2001
From: RAHEEL <mraheeliftikhar1994@gmail.com>
Date: Tue, 25 Feb 2025 17:52:05 +0500
Subject: [PATCH 09/18] fixed asset selection popup

---
 .../src/comps/controls/iconscoutControl.tsx   | 65 +++++++++----------
 1 file changed, 32 insertions(+), 33 deletions(-)

diff --git a/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx b/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
index 7c356624a9..5f72b8f2d9 100644
--- a/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
+++ b/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
@@ -22,6 +22,9 @@ import { debounce } from "lodash";
 import Spin from "antd/es/spin";
 import { ControlParams } from "./controlParams";
 import { getBase64 } from "@lowcoder-ee/util/fileUtils";
+import Flex from "antd/es/flex";
+import Typography from "antd/es/typography";
+import LoadingOutlined from "@ant-design/icons/LoadingOutlined";
 
 const ButtonWrapper = styled.div`
   width: 100%;
@@ -32,15 +35,7 @@ const ButtonIconWrapper = styled.div`
   display: flex;
   width: 18px;
 `;
-const ButtonText = styled.div`
-  margin: 0 4px;
-  flex: 1;
-  width: 0px;
-  line-height: 20px;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  text-align: left;
-`;
+
 const StyledDeleteInputIcon = styled(DeleteInputIcon)`
   margin-left: auto;
   cursor: pointer;
@@ -61,7 +56,10 @@ const Wrapper = styled.div`
   }
 `;
 const PopupContainer = styled.div`
+  display: flex;
+  flex-direction: column;
   width: 580px;
+  min-height: 480px;
   background: #ffffff;
   box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
   border-radius: 8px;
@@ -167,15 +165,6 @@ const IconWrapper = styled.div`
   justify-content: center;
 `;
 
-const IconKeyDisplay = styled.div`
-  font-size: 8px;
-  color: #8b8fa3;
-  margin-top: 4px; /* Space between the icon and the text */
-  text-align: center;
-  word-wrap: break-word; /* Ensure text wraps */
-  width: 100%; /* Ensure the container can grow */
-`;
-
 export enum AssetType {
   ICON = "icon",
   ILLUSTRATION = "illustration",
@@ -212,6 +201,7 @@ export const IconPicker = (props: {
 }) => {
   const [ visible, setVisible ] = useState(false)
   const [ loading, setLoading ] = useState(false)
+  const [ searchText, setSearchText ] = useState<string>('')
   const [ searchResults, setSearchResults ] = useState<Array<any>>([]);
 
   const onChangeRef = useRef(props.onChange);
@@ -252,14 +242,14 @@ export const IconPicker = (props: {
     }
   }
 
-  const fetchDownloadUrl = async (uuid: string) => {
+  const fetchDownloadUrl = async (uuid: string, preview: string) => {
     try {
       const result = await IconscoutApi.download(uuid, {
         format: props.assetType === AssetType.LOTTIE ? 'lottie' : 'svg',
       });
 
       downloadAsset(uuid, result.download_url, (assetUrl: string) => {
-        onChangeIcon(uuid, assetUrl, result.url);
+        onChangeIcon(uuid, assetUrl, preview);
       });
     } catch (error) {
       console.error(error);
@@ -268,6 +258,7 @@ export const IconPicker = (props: {
 
   const handleChange = debounce((e) => {
     fetchResults(e.target.value);
+    setSearchText(e.target.value);
   }, 500);
 
   const rowRenderer = useCallback(
@@ -280,7 +271,10 @@ export const IconPicker = (props: {
               key={icon.uuid}
               tabIndex={0}
               onClick={() => {
-                fetchDownloadUrl(icon.uuid);
+                fetchDownloadUrl(
+                  icon.uuid,
+                  props.assetType === AssetType.ICON ? icon.urls.png_64 : icon.urls.thumb,
+                );
               }}
             >
               <IconWrapper>
@@ -310,12 +304,8 @@ export const IconPicker = (props: {
     <Popover
       trigger={'click'}
       placement="left"
-      // align={{ offset: [props.leftOffset ?? 0, 0, 0, 0] }}
       open={visible}
       onOpenChange={setVisible}
-      // getPopupContainer={parent ? () => parent : undefined}
-      // hide the original background when dragging the popover is allowed
-      // when dragging is allowed, always re-location to avoid the popover exceeds the screen
       styles={{
         body: {
           border: "none",
@@ -339,11 +329,20 @@ export const IconPicker = (props: {
               />
               <StyledSearchIcon />
             </SearchDiv>
-            <IconListWrapper>
-              {loading && (
-                <Spin />
-              )}
-              {!loading && (
+            {loading && (
+              <Flex align="center" justify="center" style={{flex: 1}}>
+                <Spin indicator={<LoadingOutlined style={{ fontSize: 25 }} spin />} />
+              </Flex>
+            )}
+            {!loading && Boolean(searchText) && !searchResults?.length && (
+              <Flex align="center" justify="center" style={{flex: 1}}>
+                <Typography.Text type="secondary">
+                  No results found.
+                </Typography.Text>
+              </Flex>
+            )}
+            {!loading && Boolean(searchText) && searchResults?.length && (
+              <IconListWrapper>
                 <IconList
                   width={550}
                   height={400}
@@ -351,8 +350,8 @@ export const IconPicker = (props: {
                   rowCount={Math.ceil(searchResults.length / columnNum)}
                   rowRenderer={rowRenderer}
                 />
-              )}
-            </IconListWrapper>
+              </IconListWrapper>
+            )}
           </PopupContainer>
         </Draggable>
       }
@@ -365,7 +364,7 @@ export const IconPicker = (props: {
                 <video style={{'width': '100%'}} src={props.preview} autoPlay />
               )}
               {props.assetType !== AssetType.LOTTIE && (
-                <IconControlView value={props.preview} uuid={props.uuid}/>
+                <IconControlView value={props.value} uuid={props.uuid}/>
               )}
             </ButtonIconWrapper>
             <StyledDeleteInputIcon

From 5a73042562ce6d075ddbd92fa8bdb260d9b8adad Mon Sep 17 00:00:00 2001
From: RAHEEL <mraheeliftikhar1994@gmail.com>
Date: Tue, 25 Feb 2025 22:16:24 +0500
Subject: [PATCH 10/18] show free/premium assets + redirect to subscription
 page on clicking premium asset

---
 .../packages/lowcoder/src/api/iconscoutApi.ts |   4 +-
 .../comps/comps/meetingComp/controlButton.tsx |   6 +-
 .../src/comps/controls/iconscoutControl.tsx   | 101 +++++++++++++-----
 .../packages/lowcoder/src/i18n/locales/en.ts  |   9 ++
 4 files changed, 89 insertions(+), 31 deletions(-)

diff --git a/client/packages/lowcoder/src/api/iconscoutApi.ts b/client/packages/lowcoder/src/api/iconscoutApi.ts
index 0d44a7ef23..71ea45adee 100644
--- a/client/packages/lowcoder/src/api/iconscoutApi.ts
+++ b/client/packages/lowcoder/src/api/iconscoutApi.ts
@@ -9,7 +9,8 @@ export interface SearchParams {
   per_page: number;
   page: 1;
   sort: string;
-  formats: string;
+  formats?: string;
+  price?: string;
 }
 
 export type ResponseType = {
@@ -57,7 +58,6 @@ class IconscoutApi extends Api {
         withCredentials: false,
         params: {
           ...params,
-          'formats[]': params.formats,
         },
       });
     } catch (error) {
diff --git a/client/packages/lowcoder/src/comps/comps/meetingComp/controlButton.tsx b/client/packages/lowcoder/src/comps/comps/meetingComp/controlButton.tsx
index ec3f0a216a..e1d1158de0 100644
--- a/client/packages/lowcoder/src/comps/comps/meetingComp/controlButton.tsx
+++ b/client/packages/lowcoder/src/comps/comps/meetingComp/controlButton.tsx
@@ -342,13 +342,13 @@ let ButtonTmpComp = (function () {
               {children.iconSize.propertyView({
                 label: trans("button.iconSize"),
               })}
-            </Section>
-            <Section name={sectionNames.style}>
-              {children.style.getPropertyView()}
               {children.aspectRatio.propertyView({
                 label: trans("style.aspectRatio"),
               })}
             </Section>
+            <Section name={sectionNames.style}>
+              {children.style.getPropertyView()}
+            </Section>
           </>
         )}
       </>
diff --git a/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx b/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
index 5f72b8f2d9..9f60e66f3f 100644
--- a/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
+++ b/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
@@ -5,6 +5,7 @@ import {
 import {
   BlockGrayLabel,
   ControlPropertyViewWrapper,
+  CustomModal,
   DeleteInputIcon,
   TacoButton,
   TacoInput,
@@ -25,6 +26,9 @@ import { getBase64 } from "@lowcoder-ee/util/fileUtils";
 import Flex from "antd/es/flex";
 import Typography from "antd/es/typography";
 import LoadingOutlined from "@ant-design/icons/LoadingOutlined";
+import Badge from "antd/es/badge";
+import { CrownFilled } from "@ant-design/icons";
+import { SUBSCRIPTION_SETTING } from "@lowcoder-ee/constants/routesURL";
 
 const ButtonWrapper = styled.div`
   width: 100%;
@@ -125,7 +129,7 @@ const IconList = styled(List)`
 `;
 
 const IconRow = styled.div`
-  padding: 0 6px;
+  padding: 6px;
   display: flex;
   align-items: flex-start; /* Align items to the start to allow different heights */
   justify-content: space-between;
@@ -134,37 +138,56 @@ const IconRow = styled.div`
     gap: 8px;
     justify-content: flex-start;
   }
+
+  .ant-badge {
+    height: 100%;
+  }
 `;
 
 const IconItemContainer = styled.div`
   width: 60px;
+  height: 60px;
   display: flex;
   flex-direction: column;
   align-items: center; 
   justify-content: flex-start; 
   cursor: pointer;
   font-size: 28px;
-  margin-bottom: 24px; 
+  border-radius: 4px;
+  background: #fafafa;
 
   &:hover {
-    border: 1px solid #315efb;
-    border-radius: 4px;
+    box-shadow: 0 8px 24px #1a29470a,0 2px 8px #1a294714;
   }
 
   &:focus {
     border: 1px solid #315efb;
-    border-radius: 4px;
     box-shadow: 0 0 0 2px #d6e4ff;
   }
 `;
 
-const IconWrapper = styled.div`
-  height: auto;
+const IconWrapper = styled.div<{$isPremium?: boolean}>`
+  height: 100%;
   display: flex;
   align-items: center;
   justify-content: center;
+  ${props => props.$isPremium && 'opacity: 0.25' };
+`;
+
+const StyledPreviewIcon = styled.img`
+  width: auto;
+  height: auto;
+  max-width: 100%;
+  max-height: 100%;
 `;
 
+const StyledPreviewLotte = styled.video`
+  width: auto;
+  height: auto;
+  max-width: 100%;
+  max-height: 100%;
+`
+
 export enum AssetType {
   ICON = "icon",
   ILLUSTRATION = "illustration",
@@ -182,9 +205,8 @@ const IconScoutSearchParams: SearchParams = {
   query: '',
   product_type: 'item',
   asset: 'icon',
-  per_page: 50,
+  per_page: 25,
   page: 1,
-  formats: 'svg',
   sort: 'relevant',
 };
 
@@ -216,13 +238,20 @@ export const IconPicker = (props: {
 
   const fetchResults = async (query: string) => {
     setLoading(true);
-    const result = await IconscoutApi.search({
+    const freeResult = await IconscoutApi.search({
       ...IconScoutSearchParams,
       asset: props.assetType,
+      price: 'free',
+      query,
+    });
+    const premiumResult = await IconscoutApi.search({
+      ...IconScoutSearchParams,
+      asset: props.assetType,
+      price: 'premium',
       query,
     });
     setLoading(false);
-    setSearchResults(result.data);
+    setSearchResults([...freeResult.data, ...premiumResult.data]);
   };
 
   const downloadAsset = async (
@@ -271,23 +300,43 @@ export const IconPicker = (props: {
               key={icon.uuid}
               tabIndex={0}
               onClick={() => {
+                // check if premium content then show subscription popup
+                // TODO: if user has subscription then skip this if block
+                if (icon.price !== 0) {
+                  CustomModal.confirm({
+                    title: trans("iconScout.buySubscriptionTitle"),
+                    content: trans("iconScout.buySubscriptionContent"),
+                    onConfirm: () =>{
+                      window.open(SUBSCRIPTION_SETTING, "_blank");
+                    },
+                    confirmBtnType: "primary",
+                    okText: trans("iconScout.buySubscriptionButton"),
+                  })
+                  return;
+                }
+
                 fetchDownloadUrl(
                   icon.uuid,
                   props.assetType === AssetType.ICON ? icon.urls.png_64 : icon.urls.thumb,
                 );
               }}
             >
-              <IconWrapper>
-                {props.assetType === AssetType.ICON && (
-                  <img style={{'width': '100%'}} src={icon.urls.png_64} />
-                )}
-                {props.assetType === AssetType.ILLUSTRATION && (
-                  <img style={{'width': '100%'}} src={icon.urls.thumb} />
-                )}
-                {props.assetType === AssetType.LOTTIE && (
-                  <video style={{'width': '100%'}} src={icon.urls.thumb} autoPlay />
-                )}
-              </IconWrapper>
+              <Badge
+                count={icon.price !== 0 ? <CrownFilled style={{color: "#e7b549"}} /> : undefined}
+                size='small'
+              >
+                <IconWrapper $isPremium={icon.price !== 0}>
+                  {props.assetType === AssetType.ICON && (
+                    <StyledPreviewIcon src={icon.urls.png_64} />
+                  )}
+                  {props.assetType === AssetType.ILLUSTRATION && (
+                    <StyledPreviewIcon src={icon.urls.thumb} />
+                  )}
+                  {props.assetType === AssetType.LOTTIE && (
+                    <StyledPreviewLotte src={icon.urls.thumb} autoPlay />
+                  )}
+                </IconWrapper>
+              </Badge>
             </IconItemContainer>
           ))}
       </IconRow>
@@ -295,9 +344,9 @@ export const IconPicker = (props: {
   );
 
   const popupTitle = useMemo(() => {
-    if (props.assetType === AssetType.ILLUSTRATION) return 'Search Image';
-    if (props.assetType === AssetType.LOTTIE) return 'Search Animation';
-    return 'Search Icon';
+    if (props.assetType === AssetType.ILLUSTRATION) return trans("iconScout.searchImage");
+    if (props.assetType === AssetType.LOTTIE) return trans("iconScout.searchAnimation");
+    return trans("iconScout.searchIcon");
   }, [props.assetType]);
 
   return (
@@ -337,7 +386,7 @@ export const IconPicker = (props: {
             {!loading && Boolean(searchText) && !searchResults?.length && (
               <Flex align="center" justify="center" style={{flex: 1}}>
                 <Typography.Text type="secondary">
-                  No results found.
+                  {trans("iconScout.noResults")}
                 </Typography.Text>
               </Flex>
             )}
diff --git a/client/packages/lowcoder/src/i18n/locales/en.ts b/client/packages/lowcoder/src/i18n/locales/en.ts
index 735b797d4e..cfc80833f5 100644
--- a/client/packages/lowcoder/src/i18n/locales/en.ts
+++ b/client/packages/lowcoder/src/i18n/locales/en.ts
@@ -4088,6 +4088,15 @@ export const en = {
     discord: "https://discord.com/invite/qMG9uTmAx2",
   },
 
+  iconScout: {
+    "searchImage": "Search Image",
+    "searchAnimation": "Search Animation",
+    "searchIcon": "Search Icon",
+    "noResults": "No results found.",
+    "buySubscriptionTitle": "Unlock Premium Assets",
+    "buySubscriptionContent": "This asset is exclusive to Media Package Subscribers. Subscribe to Media Package and download high-quality assets without limits!",
+    "buySubscriptionButton": "Subscribe Now",
+  }
 };
 
 // const jsonString = JSON.stringify(en, null, 2);

From 6dea7b92ed32ba5489eaf07e9d9dc6ef1e6e047c Mon Sep 17 00:00:00 2001
From: RAHEEL <mraheeliftikhar1994@gmail.com>
Date: Tue, 25 Feb 2025 22:30:18 +0500
Subject: [PATCH 11/18] show loading when user select's an icon to download

---
 .../src/comps/controls/iconscoutControl.tsx   | 43 +++++++++++--------
 1 file changed, 25 insertions(+), 18 deletions(-)

diff --git a/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx b/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
index 9f60e66f3f..003e3453b3 100644
--- a/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
+++ b/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
@@ -223,6 +223,7 @@ export const IconPicker = (props: {
 }) => {
   const [ visible, setVisible ] = useState(false)
   const [ loading, setLoading ] = useState(false)
+  const [ downloading, setDownloading ] = useState(false)
   const [ searchText, setSearchText ] = useState<string>('')
   const [ searchResults, setSearchResults ] = useState<Array<any>>([]);
 
@@ -268,20 +269,24 @@ export const IconPicker = (props: {
       }
     } catch(error) {
       console.error(error);
+      setDownloading(false);
     }
   }
 
   const fetchDownloadUrl = async (uuid: string, preview: string) => {
     try {
+      setDownloading(true);
       const result = await IconscoutApi.download(uuid, {
         format: props.assetType === AssetType.LOTTIE ? 'lottie' : 'svg',
       });
 
       downloadAsset(uuid, result.download_url, (assetUrl: string) => {
+        setDownloading(false);
         onChangeIcon(uuid, assetUrl, preview);
       });
     } catch (error) {
       console.error(error);
+      setDownloading(false);
     }
   }
 
@@ -383,24 +388,26 @@ export const IconPicker = (props: {
                 <Spin indicator={<LoadingOutlined style={{ fontSize: 25 }} spin />} />
               </Flex>
             )}
-            {!loading && Boolean(searchText) && !searchResults?.length && (
-              <Flex align="center" justify="center" style={{flex: 1}}>
-                <Typography.Text type="secondary">
-                  {trans("iconScout.noResults")}
-                </Typography.Text>
-              </Flex>
-            )}
-            {!loading && Boolean(searchText) && searchResults?.length && (
-              <IconListWrapper>
-                <IconList
-                  width={550}
-                  height={400}
-                  rowHeight={80}
-                  rowCount={Math.ceil(searchResults.length / columnNum)}
-                  rowRenderer={rowRenderer}
-                />
-              </IconListWrapper>
-            )}
+            <Spin spinning={downloading} indicator={<LoadingOutlined style={{ fontSize: 25 }} />} >
+              {!loading && Boolean(searchText) && !searchResults?.length && (
+                <Flex align="center" justify="center" style={{flex: 1}}>
+                  <Typography.Text type="secondary">
+                    {trans("iconScout.noResults")}
+                  </Typography.Text>
+                </Flex>
+              )}
+              {!loading && Boolean(searchText) && searchResults?.length && (
+                <IconListWrapper>
+                  <IconList
+                    width={550}
+                    height={400}
+                    rowHeight={80}
+                    rowCount={Math.ceil(searchResults.length / columnNum)}
+                    rowRenderer={rowRenderer}
+                    />
+                </IconListWrapper>
+              )}
+            </Spin>
           </PopupContainer>
         </Draggable>
       }

From aff1582c243a1723063041dbe6860cf94445106f Mon Sep 17 00:00:00 2001
From: RAHEEL <mraheeliftikhar1994@gmail.com>
Date: Tue, 25 Feb 2025 22:30:45 +0500
Subject: [PATCH 12/18] added autoHeight and aspectRatio options in json lottie

---
 .../comps/comps/jsonComp/jsonLottieComp.tsx   | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)

diff --git a/client/packages/lowcoder/src/comps/comps/jsonComp/jsonLottieComp.tsx b/client/packages/lowcoder/src/comps/comps/jsonComp/jsonLottieComp.tsx
index 08d0b08fd3..b982fd5873 100644
--- a/client/packages/lowcoder/src/comps/comps/jsonComp/jsonLottieComp.tsx
+++ b/client/packages/lowcoder/src/comps/comps/jsonComp/jsonLottieComp.tsx
@@ -2,6 +2,7 @@ import { hiddenPropertyView, showDataLoadingIndicatorsPropertyView } from "comps
 import {
   ArrayOrJSONObjectControl,
   NumberControl,
+  StringControl,
 } from "comps/controls/codeControl";
 import { dropdownControl } from "comps/controls/dropdownControl";
 import { BoolControl } from "comps/controls/boolControl";
@@ -20,6 +21,7 @@ import { defaultLottie } from "./jsonConstants";
 import { EditorContext } from "comps/editorState";
 import { AssetType, IconscoutControl } from "@lowcoder-ee/comps/controls/iconscoutControl";
 import { DotLottie } from "@lottiefiles/dotlottie-react";
+import { AutoHeightControl } from "@lowcoder-ee/comps/controls/autoHeightControl";
 
 // const Player = lazy(
 //   () => import('@lottiefiles/react-lottie-player')
@@ -112,6 +114,8 @@ let JsonLottieTmpComp = (function () {
     animationStart: dropdownControl(animationStartOptions, "auto"),
     loop: dropdownControl(loopOptions, "single"),
     keepLastFrame: BoolControl.DEFAULT_TRUE,
+    autoHeight: withDefault(AutoHeightControl, "fixed"),
+    aspectRatio: withDefault(StringControl, "16 / 9"),
   };
   return new UICompBuilder(childrenMap, (props, dispatch) => {
     const [dotLottie, setDotLottie] = useState<DotLottie | null>(null);
@@ -151,6 +155,7 @@ let JsonLottieTmpComp = (function () {
             background: `${props.container.background}`,
             padding: `${props.container.padding}`,
             rotate: props.container.rotation,
+            aspectRatio: props.aspectRatio,
           }}
         >
           <DotLottiePlayer
@@ -171,6 +176,9 @@ let JsonLottieTmpComp = (function () {
             }}
             onMouseEnter={() => props.animationStart === "hover" && dotLottie?.play()}
             onMouseLeave={() => props.animationStart === "hover" && dotLottie?.pause()}
+            renderConfig={{
+              autoResize: props.autoHeight,
+            }}
           />
         </div>
       </div>
@@ -204,6 +212,15 @@ let JsonLottieTmpComp = (function () {
             </>
           )}
 
+          {["layout", "both"].includes(useContext(EditorContext).editorModeStatus) && (
+            <Section name={sectionNames.layout}>
+              {children.autoHeight.getPropertyView()}
+              {children.aspectRatio.propertyView({
+                label: trans("style.aspectRatio"),
+              })}
+            </Section>
+          )}
+
           {(useContext(EditorContext).editorModeStatus === "layout" || useContext(EditorContext).editorModeStatus === "both") && (
             <>
               <Section name={sectionNames.style}>
@@ -221,7 +238,7 @@ let JsonLottieTmpComp = (function () {
 })();
 JsonLottieTmpComp = class extends JsonLottieTmpComp {
   override autoHeight(): boolean {
-    return false;
+    return this.children.autoHeight.getView();
   }
 };
 export const JsonLottieComp = withExposingConfigs(JsonLottieTmpComp, [

From 0dc69c6d4d710669892ecfb98cc181f19c54a980 Mon Sep 17 00:00:00 2001
From: RAHEEL <mraheeliftikhar1994@gmail.com>
Date: Wed, 26 Feb 2025 12:57:10 +0500
Subject: [PATCH 13/18] fixed selected icon preview in controlButton

---
 .../lowcoder/src/comps/comps/meetingComp/controlButton.tsx     | 3 +--
 .../packages/lowcoder/src/comps/controls/iconscoutControl.tsx  | 1 +
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/client/packages/lowcoder/src/comps/comps/meetingComp/controlButton.tsx b/client/packages/lowcoder/src/comps/comps/meetingComp/controlButton.tsx
index e1d1158de0..0e31deb507 100644
--- a/client/packages/lowcoder/src/comps/comps/meetingComp/controlButton.tsx
+++ b/client/packages/lowcoder/src/comps/comps/meetingComp/controlButton.tsx
@@ -77,14 +77,13 @@ const IconWrapper = styled.div<{ $style: any }>`
 
 const IconScoutWrapper = styled.div<{ $style: any }>`
   display: flex;
-  height: 100%;
 
   ${(props) => props.$style && getStyleIcon(props.$style)}
 `;
 
 function getStyleIcon(style: any) {
   return css`
-    svg {
+    svg, img {
       width: ${style.size} !important;
       height: ${style.size} !important;
     }
diff --git a/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx b/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
index 003e3453b3..5814c22e2f 100644
--- a/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
+++ b/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
@@ -51,6 +51,7 @@ const StyledDeleteInputIcon = styled(DeleteInputIcon)`
 
 const StyledImage = styled.img`
   height: 100%;
+  width: 100%;
   color: currentColor;
 `;
 

From 6886a0ee354cac603d848f212775c7d9addc19be Mon Sep 17 00:00:00 2001
From: RAHEEL <mraheeliftikhar1994@gmail.com>
Date: Wed, 26 Feb 2025 22:53:49 +0500
Subject: [PATCH 14/18] added fit/align controls in json lottie comp

---
 .../comps/comps/jsonComp/jsonLottieComp.tsx   | 64 +++++++++++++++++--
 .../src/comps/controls/iconscoutControl.tsx   |  4 +-
 .../packages/lowcoder/src/i18n/locales/en.ts  |  4 +-
 3 files changed, 62 insertions(+), 10 deletions(-)

diff --git a/client/packages/lowcoder/src/comps/comps/jsonComp/jsonLottieComp.tsx b/client/packages/lowcoder/src/comps/comps/jsonComp/jsonLottieComp.tsx
index b982fd5873..7ecec0426c 100644
--- a/client/packages/lowcoder/src/comps/comps/jsonComp/jsonLottieComp.tsx
+++ b/client/packages/lowcoder/src/comps/comps/jsonComp/jsonLottieComp.tsx
@@ -22,6 +22,7 @@ import { EditorContext } from "comps/editorState";
 import { AssetType, IconscoutControl } from "@lowcoder-ee/comps/controls/iconscoutControl";
 import { DotLottie } from "@lottiefiles/dotlottie-react";
 import { AutoHeightControl } from "@lowcoder-ee/comps/controls/autoHeightControl";
+import { useResizeDetector } from "react-resize-detector";
 
 // const Player = lazy(
 //   () => import('@lottiefiles/react-lottie-player')
@@ -93,6 +94,27 @@ const speedOptions = [
   },
 ] as const;
 
+const alignOptions = [
+  { label: "None", value: "none" },
+  { label: "Fill", value: "fill" },
+  { label: "Cover", value: "cover" },
+  { label: "Contain", value: "contain" },
+  { label: "Fit Width", value: "fit-width" },
+  { label: "Fit Height", value: "fit-height" },
+] as const;
+
+const fitOptions = [
+  { label: "Top Left", value: "0,0" },
+  { label: "Top Center", value: "0.5,0" },
+  { label: "Top Right", value: "1,0" },
+  { label: "Center Left", value: "0,0.5" },
+  { label: "Center", value: "0.5,0.5" },
+  { label: "Center Right", value: "1,0.5" },
+  { label: "Bottom Left", value: "0,1" },
+  { label: "Bottom Center", value: "0.5,1" },
+  { label: "Bottom Right", value: "1,1" },
+] as const;
+
 const ModeOptions = [
   { label: "Lottie JSON", value: "standard" },
   { label: "Asset Library", value: "asset-library" }
@@ -114,30 +136,59 @@ let JsonLottieTmpComp = (function () {
     animationStart: dropdownControl(animationStartOptions, "auto"),
     loop: dropdownControl(loopOptions, "single"),
     keepLastFrame: BoolControl.DEFAULT_TRUE,
-    autoHeight: withDefault(AutoHeightControl, "fixed"),
-    aspectRatio: withDefault(StringControl, "16 / 9"),
+    autoHeight: withDefault(AutoHeightControl, "auto"),
+    aspectRatio: withDefault(StringControl, "1/1"),
+    fit: dropdownControl(alignOptions, "contain"),
+    align: dropdownControl(fitOptions, "0.5,0.5"),
   };
   return new UICompBuilder(childrenMap, (props, dispatch) => {
     const [dotLottie, setDotLottie] = useState<DotLottie | null>(null);
-    
+
+    const setLayoutAndResize = () => {
+      const align = props.align.split(',');
+      dotLottie?.setLayout({fit: props.fit, align: [Number(align[0]), Number(align[1])]})
+      dotLottie?.resize();
+    }
+
+    const { ref: wrapperRef } = useResizeDetector({
+      onResize: () => {
+        if (dotLottie) {
+          setLayoutAndResize();
+        }
+      }
+    });
+
     useEffect(() => {
       const onComplete = () => {
         props.keepLastFrame && dotLottie?.setFrame(100);
       }
 
+      const onLoad = () => {
+        setLayoutAndResize();
+      }
+
       if (dotLottie) {
         dotLottie.addEventListener('complete', onComplete);
+        dotLottie.addEventListener('load', onLoad);
       }
   
       return () => {
         if (dotLottie) {
           dotLottie.removeEventListener('complete', onComplete);
+          dotLottie.removeEventListener('load', onLoad);
         }
       };
     }, [dotLottie, props.keepLastFrame]);
 
+    useEffect(() => {
+      if (dotLottie) {
+        setLayoutAndResize();
+      }
+    }, [dotLottie, props.fit, props.align, props.autoHeight]);
+
     return (
       <div
+        ref={wrapperRef}
         style={{
           height: '100%',
           padding: `${props.container.margin}`,
@@ -155,7 +206,6 @@ let JsonLottieTmpComp = (function () {
             background: `${props.container.background}`,
             padding: `${props.container.padding}`,
             rotate: props.container.rotation,
-            aspectRatio: props.aspectRatio,
           }}
         >
           <DotLottiePlayer
@@ -173,12 +223,10 @@ let JsonLottieTmpComp = (function () {
               width: "100%",
               maxWidth: "100%",
               maxHeight: "100%",
+              aspectRatio: props.aspectRatio,
             }}
             onMouseEnter={() => props.animationStart === "hover" && dotLottie?.play()}
             onMouseLeave={() => props.animationStart === "hover" && dotLottie?.pause()}
-            renderConfig={{
-              autoResize: props.autoHeight,
-            }}
           />
         </div>
       </div>
@@ -218,6 +266,8 @@ let JsonLottieTmpComp = (function () {
               {children.aspectRatio.propertyView({
                 label: trans("style.aspectRatio"),
               })}
+              {children.align.propertyView({ label: trans("jsonLottie.align")})}
+              {children.fit.propertyView({ label: trans("jsonLottie.fit")})}
             </Section>
           )}
 
diff --git a/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx b/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
index 5814c22e2f..e3a590450e 100644
--- a/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
+++ b/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
@@ -390,14 +390,14 @@ export const IconPicker = (props: {
               </Flex>
             )}
             <Spin spinning={downloading} indicator={<LoadingOutlined style={{ fontSize: 25 }} />} >
-              {!loading && Boolean(searchText) && !searchResults?.length && (
+              {!loading && Boolean(searchText) && !Boolean(searchResults?.length) && (
                 <Flex align="center" justify="center" style={{flex: 1}}>
                   <Typography.Text type="secondary">
                     {trans("iconScout.noResults")}
                   </Typography.Text>
                 </Flex>
               )}
-              {!loading && Boolean(searchText) && searchResults?.length && (
+              {!loading && Boolean(searchText) && Boolean(searchResults?.length) && (
                 <IconListWrapper>
                   <IconList
                     width={550}
diff --git a/client/packages/lowcoder/src/i18n/locales/en.ts b/client/packages/lowcoder/src/i18n/locales/en.ts
index cfc80833f5..8946f0bf66 100644
--- a/client/packages/lowcoder/src/i18n/locales/en.ts
+++ b/client/packages/lowcoder/src/i18n/locales/en.ts
@@ -3783,7 +3783,9 @@ export const en = {
     "onHover": "On Hover",
     "singlePlay": "Single Play",
     "endlessLoop": "Endless Loop",
-    "keepLastFrame": "Keep Last Frame displayed"
+    "keepLastFrame": "Keep Last Frame displayed",
+    "fit": "Fit",
+    "align": "Align",
   },
   "timeLine": {
     "titleColor": "Title Color",

From a29d73537f8b765a5577001cc92120931e5131df Mon Sep 17 00:00:00 2001
From: FalkWolsky <fw@falkwolsky.com>
Date: Wed, 26 Feb 2025 21:43:09 +0100
Subject: [PATCH 15/18] Changing to Flow API

---
 .../packages/lowcoder/src/api/iconFlowApi.ts  | 163 ++++++++++++++++++
 .../packages/lowcoder/src/api/iconscoutApi.ts |  75 +-------
 .../lowcoder/src/api/subscriptionApi.ts       |   5 -
 .../src/comps/controls/iconscoutControl.tsx   |  28 +--
 4 files changed, 184 insertions(+), 87 deletions(-)
 create mode 100644 client/packages/lowcoder/src/api/iconFlowApi.ts

diff --git a/client/packages/lowcoder/src/api/iconFlowApi.ts b/client/packages/lowcoder/src/api/iconFlowApi.ts
new file mode 100644
index 0000000000..5bf664c220
--- /dev/null
+++ b/client/packages/lowcoder/src/api/iconFlowApi.ts
@@ -0,0 +1,163 @@
+import Api from "api/api";
+import axios, { AxiosInstance, AxiosPromise, AxiosRequestConfig } from "axios";
+import { calculateFlowCode }  from "./apiUtils";
+
+export interface SearchParams {
+  query: string;
+  asset: string;
+  per_page: number;
+  page: 1;
+  sort: string;
+  formats?: string;
+  price?: string;
+}
+
+export type ResponseType = {
+  response: any;
+};
+
+const lcHeaders = {
+  "Lowcoder-Token": calculateFlowCode(),
+  "Content-Type": "application/json"
+};
+
+let axiosIns: AxiosInstance | null = null;
+
+const getAxiosInstance = (clientSecret?: string) => {
+  if (axiosIns && !clientSecret) {
+    return axiosIns;
+  }
+
+  const headers: Record<string, string> = {
+    "Content-Type": "application/json",
+  };
+
+  const apiRequestConfig: AxiosRequestConfig = {
+    baseURL: "https://api-service.lowcoder.cloud/api/flow",
+    headers,
+  };
+
+  axiosIns = axios.create(apiRequestConfig);
+  return axiosIns;
+}
+
+class IconFlowApi extends Api {
+
+  static async secureRequest(body: any, timeout: number = 6000): Promise<any> {
+    let response;
+    const axiosInstance = getAxiosInstance();
+
+    // Create a cancel token and set timeout for cancellation
+    const source = axios.CancelToken.source();
+    const timeoutId = setTimeout(() => {
+      source.cancel("Request timed out.");
+    }, timeout);
+
+    // Request configuration with cancel token
+    const requestConfig: AxiosRequestConfig = {
+      method: "POST",
+      withCredentials: true,
+      data: body,
+      cancelToken: source.token, // Add cancel token
+    };
+
+    try {
+      response = await axiosInstance.request(requestConfig);
+    } catch (error) {
+      if (axios.isCancel(error)) {
+        // Retry once after timeout cancellation
+        try {
+          // Reset the cancel token and retry
+          const retrySource = axios.CancelToken.source();
+          const retryTimeoutId = setTimeout(() => {
+            retrySource.cancel("Retry request timed out.");
+          }, 10000);
+
+          response = await axiosInstance.request({
+            ...requestConfig,
+            cancelToken: retrySource.token,
+          });
+
+          clearTimeout(retryTimeoutId);
+        } catch (retryError) {
+          console.warn("Error at Secure Flow Request. Retry failed:", retryError);
+          throw retryError;
+        }
+      } else {
+        console.warn("Error at Secure Flow Request:", error);
+        throw error;
+      }
+    } finally {
+      clearTimeout(timeoutId); // Clear the initial timeout
+    }
+
+    return response;
+  }
+
+}
+
+export const searchAssets = async (searchParameters : SearchParams) => {
+  const apiBody = {
+    path: "webhook/scout/search-asset",
+    data: searchParameters,
+    method: "post",
+    headers: lcHeaders
+  };
+  try {
+    const result = await IconFlowApi.secureRequest(apiBody);
+    return result?.response?.items?.total > 0 ? result.response.items as any : null;
+  } catch (error) {
+    console.error("Error searching Design Assets:", error);
+    throw error;
+  }
+};
+
+export const getAssetLinks = async (uuid: string, params: Record<string, string>) => {
+  const apiBody = {
+    path: "webhook/scout/get-asset-links",
+    data: params,
+    method: "post",
+    headers: lcHeaders
+  };
+  try {
+    const result = await IconFlowApi.secureRequest(apiBody);
+
+    return result?.response?.items?.total > 0 ? result.response.items as any : null;
+  } catch (error) {
+    console.error("Error searching Design Assets:", error);
+    throw error;
+  }
+};
+
+
+/*
+
+static async search(params: SearchParams): Promise<any> {
+    let response;
+    try {
+      response = await getAxiosInstance().request({
+        url: '/v3/search',
+        method: "GET",
+        withCredentials: false,
+        params: {
+          ...params,
+        },
+      });
+    } catch (error) {
+      console.error(error);
+    }
+    return response?.data.response.items;
+  }
+
+  static async download(uuid: string, params: Record<string, string>): Promise<any> {
+    const response = await getAxiosInstance(clientSecret).request({
+      url: `/v3/items/${uuid}/api-download?format=${params.format}`,
+      method: "POST",
+      withCredentials: false,
+    });
+    return response?.data.response.download;
+  }
+
+*/ 
+
+export default IconFlowApi;
\ No newline at end of file
diff --git a/client/packages/lowcoder/src/api/iconscoutApi.ts b/client/packages/lowcoder/src/api/iconscoutApi.ts
index 71ea45adee..0ad5bf2569 100644
--- a/client/packages/lowcoder/src/api/iconscoutApi.ts
+++ b/client/packages/lowcoder/src/api/iconscoutApi.ts
@@ -1,84 +1,15 @@
 import Api from "api/api";
-import axios, { AxiosInstance, AxiosPromise, AxiosRequestConfig } from "axios";
-import { GenericApiResponse } from "./apiResponses";
-
-export interface SearchParams {
-  query: string;
-  product_type: string;
-  asset: string;
-  per_page: number;
-  page: 1;
-  sort: string;
-  formats?: string;
-  price?: string;
-}
+import axios from "axios";
 
 export type ResponseType = {
   response: any;
 };
 
-const apiUrl = "https://api.iconscout.com";
-const clientID = "";
-const clientSecret = "";
-const currentPage = 1;
-const currentQuery = '';
-const currentData = [];
-
-let axiosIns: AxiosInstance | null = null;
-
-const getAxiosInstance = (clientSecret?: string) => {
-  if (axiosIns && !clientSecret) {
-    return axiosIns;
-  }
-
-  const headers: Record<string, string> = {
-    "Content-Type": "application/json",
-    "Client-ID": clientID,
-  }
-  if (clientSecret) {
-    headers['Client-Secret'] = clientSecret;
-  }
-  const apiRequestConfig: AxiosRequestConfig = {
-    baseURL: `${apiUrl}`,
-    headers,
-    withCredentials: true,
-  };
-
-  axiosIns = axios.create(apiRequestConfig);
-  return axiosIns;
-}
-
-class IconscoutApi extends Api {
-  static async search(params: SearchParams): Promise<any> {
-    let response;
-    try {
-      response = await getAxiosInstance().request({
-        url: '/v3/search',
-        method: "GET",
-        withCredentials: false,
-        params: {
-          ...params,
-        },
-      });
-    } catch (error) {
-      console.error(error);
-    }
-    return response?.data.response.items;
-  }
-
-  static async download(uuid: string, params: Record<string, string>): Promise<any> {
-    const response = await getAxiosInstance(clientSecret).request({
-      url: `/v3/items/${uuid}/api-download?format=${params.format}`,
-      method: "POST",
-      withCredentials: false,
-    });
-    return response?.data.response.download;
-  }
-
+class IconScoutApi extends Api {
   static async downloadAsset(url: string): Promise<any> {
     const response = await axios.get(url, {responseType: 'blob'})
     return response?.data;
   }
 }
 
-export default IconscoutApi;
+export default IconScoutApi;
\ No newline at end of file
diff --git a/client/packages/lowcoder/src/api/subscriptionApi.ts b/client/packages/lowcoder/src/api/subscriptionApi.ts
index 6bfcdb2599..7e19c8f19d 100644
--- a/client/packages/lowcoder/src/api/subscriptionApi.ts
+++ b/client/packages/lowcoder/src/api/subscriptionApi.ts
@@ -1,11 +1,6 @@
 import Api from "api/api";
 import axios, { AxiosInstance, AxiosRequestConfig, CancelToken } from "axios";
-import { useDispatch, useSelector } from "react-redux";
-import { useEffect, useState} from "react";
 import { calculateFlowCode }  from "./apiUtils";
-import { fetchGroupsAction, fetchOrgUsersAction } from "redux/reduxActions/orgActions";
-import { getOrgUsers } from "redux/selectors/orgSelectors";
-import { AppState } from "@lowcoder-ee/redux/reducers";
 import type {
   LowcoderNewCustomer,
   LowcoderSearchCustomer,
diff --git a/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx b/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
index e3a590450e..08ca10c53d 100644
--- a/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
+++ b/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
@@ -17,7 +17,8 @@ import styled from "styled-components";
 import Popover from "antd/es/popover";
 import { CloseIcon, SearchIcon } from "icons";
 import Draggable from "react-draggable";
-import IconscoutApi, { SearchParams } from "api/iconscoutApi";
+import IconScoutApi from "@lowcoder-ee/api/iconScoutApi";
+import { searchAssets, getAssetLinks, SearchParams } from "@lowcoder-ee/api/iconFlowApi";
 import List, { ListRowProps } from "react-virtualized/dist/es/List";
 import { debounce } from "lodash";
 import Spin from "antd/es/spin";
@@ -204,7 +205,6 @@ export type IconScoutAsset = {
 
 const IconScoutSearchParams: SearchParams = {
   query: '',
-  product_type: 'item',
   asset: 'icon',
   per_page: 25,
   page: 1,
@@ -240,13 +240,13 @@ export const IconPicker = (props: {
 
   const fetchResults = async (query: string) => {
     setLoading(true);
-    const freeResult = await IconscoutApi.search({
+    const freeResult = await searchAssets({
       ...IconScoutSearchParams,
       asset: props.assetType,
       price: 'free',
       query,
     });
-    const premiumResult = await IconscoutApi.search({
+    const premiumResult = await searchAssets({
       ...IconScoutSearchParams,
       asset: props.assetType,
       price: 'premium',
@@ -263,7 +263,7 @@ export const IconPicker = (props: {
   ) => {
     try {
       if (uuid && downloadUrl) {
-        const json = await IconscoutApi.downloadAsset(downloadUrl);
+        const json = await IconScoutApi.downloadAsset(downloadUrl);
         getBase64(json, (url: string) => {
           callback(url);
         });
@@ -277,7 +277,7 @@ export const IconPicker = (props: {
   const fetchDownloadUrl = async (uuid: string, preview: string) => {
     try {
       setDownloading(true);
-      const result = await IconscoutApi.download(uuid, {
+      const result = await getAssetLinks(uuid, {
         format: props.assetType === AssetType.LOTTIE ? 'lottie' : 'svg',
       });
 
@@ -291,10 +291,18 @@ export const IconPicker = (props: {
     }
   }
 
-  const handleChange = debounce((e) => {
-    fetchResults(e.target.value);
-    setSearchText(e.target.value);
-  }, 500);
+  const handleChange = (e: { target: { value: any; }; }) => {
+    const query = e.target.value;
+    setSearchText(query); // Update search text immediately
+  
+    if (query.length > 2) {
+      debouncedFetchResults(query); // Trigger search only for >2 characters
+    } else {
+      setSearchResults([]); // Clear results if input is too short
+    }
+  };
+
+  const debouncedFetchResults = useMemo(() => debounce(fetchResults, 700), []);
 
   const rowRenderer = useCallback(
     (p: ListRowProps) => (

From 516d9d216e67b0721244eb053f1df853e6ddf602 Mon Sep 17 00:00:00 2001
From: FalkWolsky <fw@falkwolsky.com>
Date: Wed, 26 Feb 2025 23:04:39 +0100
Subject: [PATCH 16/18] Search and AssetURL APis changed to Flow Endpoint

---
 client/packages/lowcoder/src/api/iconFlowApi.ts           | 8 ++++----
 .../lowcoder/src/comps/controls/iconscoutControl.tsx      | 3 +++
 2 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/client/packages/lowcoder/src/api/iconFlowApi.ts b/client/packages/lowcoder/src/api/iconFlowApi.ts
index 5bf664c220..ba6e6bea0f 100644
--- a/client/packages/lowcoder/src/api/iconFlowApi.ts
+++ b/client/packages/lowcoder/src/api/iconFlowApi.ts
@@ -71,7 +71,7 @@ class IconFlowApi extends Api {
           const retrySource = axios.CancelToken.source();
           const retryTimeoutId = setTimeout(() => {
             retrySource.cancel("Retry request timed out.");
-          }, 10000);
+          }, 20000);
 
           response = await axiosInstance.request({
             ...requestConfig,
@@ -105,7 +105,7 @@ export const searchAssets = async (searchParameters : SearchParams) => {
   };
   try {
     const result = await IconFlowApi.secureRequest(apiBody);
-    return result?.response?.items?.total > 0 ? result.response.items as any : null;
+    return result?.data?.response?.items?.total > 0 ? result.data.response.items as any : null;
   } catch (error) {
     console.error("Error searching Design Assets:", error);
     throw error;
@@ -115,14 +115,14 @@ export const searchAssets = async (searchParameters : SearchParams) => {
 export const getAssetLinks = async (uuid: string, params: Record<string, string>) => {
   const apiBody = {
     path: "webhook/scout/get-asset-links",
-    data: params,
+    data: {"uuid" : uuid, "params" : params},
     method: "post",
     headers: lcHeaders
   };
   try {
     const result = await IconFlowApi.secureRequest(apiBody);
 
-    return result?.response?.items?.total > 0 ? result.response.items as any : null;
+    return result?.data?.response?.download?.url.length > 0 ? result.data.response.download as any : null;
   } catch (error) {
     console.error("Error searching Design Assets:", error);
     throw error;
diff --git a/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx b/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
index 08ca10c53d..cefb85b3a1 100644
--- a/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
+++ b/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
@@ -253,6 +253,9 @@ export const IconPicker = (props: {
       query,
     });
     setLoading(false);
+
+    console.log("freeResult", freeResult, "premiumResult", premiumResult)
+
     setSearchResults([...freeResult.data, ...premiumResult.data]);
   };
 

From 4b50843077979d9a0a11852c702454b210a7b13f Mon Sep 17 00:00:00 2001
From: RAHEEL <mraheeliftikhar1994@gmail.com>
Date: Thu, 27 Feb 2025 22:39:50 +0500
Subject: [PATCH 17/18] added events + exposed play/pause/stop methods with
 json lottie comp

---
 .../comps/comps/jsonComp/jsonLottieComp.tsx   | 87 +++++++++++++++++--
 .../src/comps/controls/iconscoutControl.tsx   |  2 +-
 .../packages/lowcoder/src/i18n/locales/en.ts  |  6 ++
 3 files changed, 87 insertions(+), 8 deletions(-)

diff --git a/client/packages/lowcoder/src/comps/comps/jsonComp/jsonLottieComp.tsx b/client/packages/lowcoder/src/comps/comps/jsonComp/jsonLottieComp.tsx
index 7ecec0426c..4cb3881beb 100644
--- a/client/packages/lowcoder/src/comps/comps/jsonComp/jsonLottieComp.tsx
+++ b/client/packages/lowcoder/src/comps/comps/jsonComp/jsonLottieComp.tsx
@@ -11,7 +11,7 @@ import { AnimationStyle, LottieStyle } from "comps/controls/styleControlConstant
 import { trans } from "i18n";
 import { Section, sectionNames } from "lowcoder-design";
 import { useContext, lazy, useEffect, useState } from "react";  
-import { UICompBuilder, withDefault } from "../../generators";
+import { stateComp, UICompBuilder, withDefault } from "../../generators";
 import {
   NameConfig,
   NameConfigHidden,
@@ -23,6 +23,9 @@ import { AssetType, IconscoutControl } from "@lowcoder-ee/comps/controls/iconsco
 import { DotLottie } from "@lottiefiles/dotlottie-react";
 import { AutoHeightControl } from "@lowcoder-ee/comps/controls/autoHeightControl";
 import { useResizeDetector } from "react-resize-detector";
+import { eventHandlerControl } from "@lowcoder-ee/comps/controls/eventHandlerControl";
+import { withMethodExposing } from "@lowcoder-ee/comps/generators/withMethodExposing";
+import { changeChildAction } from "lowcoder-core";
 
 // const Player = lazy(
 //   () => import('@lottiefiles/react-lottie-player')
@@ -46,6 +49,10 @@ const animationStartOptions = [
     label: trans("jsonLottie.onHover"),
     value: "hover",
   },
+  {
+    label: trans("jsonLottie.onTrigger"),
+    value: "trigger",
+  },
 ] as const;
 
 const loopOptions = [
@@ -120,6 +127,14 @@ const ModeOptions = [
   { label: "Asset Library", value: "asset-library" }
 ] as const;
 
+const EventOptions = [
+  { label: trans("jsonLottie.load"), value: "load", description: trans("jsonLottie.load") },
+  { label: trans("jsonLottie.play"), value: "play", description: trans("jsonLottie.play") },
+  { label: trans("jsonLottie.pause"), value: "pause", description: trans("jsonLottie.pause") },
+  { label: trans("jsonLottie.stop"), value: "stop", description: trans("jsonLottie.stop") },
+  { label: trans("jsonLottie.complete"), value: "complete", description: trans("jsonLottie.complete") },
+] as const;;
+
 let JsonLottieTmpComp = (function () {
   const childrenMap = {
     sourceMode: dropdownControl(ModeOptions, "standard"),
@@ -140,6 +155,8 @@ let JsonLottieTmpComp = (function () {
     aspectRatio: withDefault(StringControl, "1/1"),
     fit: dropdownControl(alignOptions, "contain"),
     align: dropdownControl(fitOptions, "0.5,0.5"),
+    onEvent: eventHandlerControl(EventOptions),
+    dotLottieRef: stateComp<any | null>(null),
   };
   return new UICompBuilder(childrenMap, (props, dispatch) => {
     const [dotLottie, setDotLottie] = useState<DotLottie | null>(null);
@@ -161,21 +178,41 @@ let JsonLottieTmpComp = (function () {
     useEffect(() => {
       const onComplete = () => {
         props.keepLastFrame && dotLottie?.setFrame(100);
+        props.onEvent('complete');
       }
 
       const onLoad = () => {
         setLayoutAndResize();
+        props.onEvent('load');
+      }
+
+      const onPlay = () => {
+        props.onEvent('play');
+      }
+
+      const onPause = () => {
+        props.onEvent('pause');
+      }
+
+      const onStop = () => {
+        props.onEvent('stop');
       }
 
       if (dotLottie) {
         dotLottie.addEventListener('complete', onComplete);
         dotLottie.addEventListener('load', onLoad);
+        dotLottie.addEventListener('play', onPlay);
+        dotLottie.addEventListener('pause', onPause);
+        dotLottie.addEventListener('stop', onStop);
       }
   
       return () => {
         if (dotLottie) {
           dotLottie.removeEventListener('complete', onComplete);
           dotLottie.removeEventListener('load', onLoad);
+          dotLottie.removeEventListener('play', onPlay);
+          dotLottie.removeEventListener('pause', onPause);
+          dotLottie.removeEventListener('stop', onStop);
         }
       };
     }, [dotLottie, props.keepLastFrame]);
@@ -212,17 +249,18 @@ let JsonLottieTmpComp = (function () {
             key={
               [props.speed, props.animationStart, props.loop, props.value, props.keepLastFrame] as any
             }
-            dotLottieRefCallback={setDotLottie}
+            dotLottieRefCallback={(lottieRef) => {
+              setDotLottie(lottieRef);
+              dispatch(
+                changeChildAction("dotLottieRef", lottieRef as any, false)
+              )
+            }}
             autoplay={props.animationStart === "auto"}
             loop={props.loop === "single" ? false : true}
             speed={Number(props.speed)}
             data={props.sourceMode === 'standard' ? props.value as Record<string, undefined> : undefined}
             src={props.sourceMode === 'asset-library' ? props.iconScoutAsset?.value : undefined}
             style={{
-              height: "100%",
-              width: "100%",
-              maxWidth: "100%",
-              maxHeight: "100%",
               aspectRatio: props.aspectRatio,
             }}
             onMouseEnter={() => props.animationStart === "hover" && dotLottie?.play()}
@@ -250,11 +288,12 @@ let JsonLottieTmpComp = (function () {
 
           {(useContext(EditorContext).editorModeStatus === "logic" || useContext(EditorContext).editorModeStatus === "both") && (
             <><Section name={sectionNames.interaction}>
+                {children.onEvent.getPropertyView()}
                 {children.speed.propertyView({ label: trans("jsonLottie.speed")})}
                 {children.loop.propertyView({ label: trans("jsonLottie.loop")})}
                 {children.animationStart.propertyView({ label: trans("jsonLottie.animationStart")})}
-                 {children.keepLastFrame.propertyView({ label: trans("jsonLottie.keepLastFrame")})}
                 {hiddenPropertyView(children)}
+                {children.keepLastFrame.propertyView({ label: trans("jsonLottie.keepLastFrame")})}
                 {showDataLoadingIndicatorsPropertyView(children)}
               </Section>
             </>
@@ -291,6 +330,40 @@ JsonLottieTmpComp = class extends JsonLottieTmpComp {
     return this.children.autoHeight.getView();
   }
 };
+
+JsonLottieTmpComp = withMethodExposing(JsonLottieTmpComp, [
+  {
+    method: {
+      name: "play",
+      description: trans("jsonLottie.play"),
+      params: [],
+    },
+    execute: (comp) => {
+      (comp.children.dotLottieRef.value as unknown as DotLottie)?.play();
+    },
+  },
+  {
+    method: {
+      name: "pause",
+      description: trans("jsonLottie.pause"),
+      params: [],
+    },
+    execute: (comp) => {
+      (comp.children.dotLottieRef.value as unknown as DotLottie)?.pause();
+    },
+  },
+  {
+    method: {
+      name: "stop",
+      description: trans("jsonLottie.stop"),
+      params: [],
+    },
+    execute: (comp) => {
+      (comp.children.dotLottieRef.value as unknown as DotLottie)?.stop();
+    },
+  },
+]);
+
 export const JsonLottieComp = withExposingConfigs(JsonLottieTmpComp, [
   new NameConfig("value", trans("jsonLottie.valueDesc")),
   NameConfigHidden,
diff --git a/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx b/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
index cefb85b3a1..3370788ac9 100644
--- a/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
+++ b/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
@@ -17,7 +17,7 @@ import styled from "styled-components";
 import Popover from "antd/es/popover";
 import { CloseIcon, SearchIcon } from "icons";
 import Draggable from "react-draggable";
-import IconScoutApi from "@lowcoder-ee/api/iconScoutApi";
+import IconScoutApi from "@lowcoder-ee/api/iconscoutApi";
 import { searchAssets, getAssetLinks, SearchParams } from "@lowcoder-ee/api/iconFlowApi";
 import List, { ListRowProps } from "react-virtualized/dist/es/List";
 import { debounce } from "lodash";
diff --git a/client/packages/lowcoder/src/i18n/locales/en.ts b/client/packages/lowcoder/src/i18n/locales/en.ts
index 8946f0bf66..3c82c2212b 100644
--- a/client/packages/lowcoder/src/i18n/locales/en.ts
+++ b/client/packages/lowcoder/src/i18n/locales/en.ts
@@ -3781,11 +3781,17 @@ export const en = {
     "loop": "Loop",
     "auto": "Auto",
     "onHover": "On Hover",
+    "onTrigger": "On Trigger",
     "singlePlay": "Single Play",
     "endlessLoop": "Endless Loop",
     "keepLastFrame": "Keep Last Frame displayed",
     "fit": "Fit",
     "align": "Align",
+    "load": "On Load",
+    "play": "On Play",
+    "pause": "On Pause",
+    "stop": "On Stop",
+    "complete": "On Complete",
   },
   "timeLine": {
     "titleColor": "Title Color",

From 99f572515e29285e72e4a6096a1f647fe8ca2a1c Mon Sep 17 00:00:00 2001
From: RAHEEL <mraheeliftikhar1994@gmail.com>
Date: Sat, 19 Apr 2025 01:52:59 +0500
Subject: [PATCH 18/18] allow media pack subscribers to use icon scout assets

---
 client/packages/lowcoder/src/app.tsx          |  57 ++--
 .../src/comps/controls/iconscoutControl.tsx   |   9 +-
 .../src/pages/ApplicationV2/index.tsx         | 315 +++++++++---------
 3 files changed, 194 insertions(+), 187 deletions(-)

diff --git a/client/packages/lowcoder/src/app.tsx b/client/packages/lowcoder/src/app.tsx
index 05dbeaab25..1ace7b0225 100644
--- a/client/packages/lowcoder/src/app.tsx
+++ b/client/packages/lowcoder/src/app.tsx
@@ -60,6 +60,7 @@ import GlobalInstances from 'components/GlobalInstances';
 import { fetchHomeData, fetchServerSettingsAction } from "./redux/reduxActions/applicationActions";
 import { getNpmPackageMeta } from "./comps/utils/remote";
 import { packageMetaReadyAction, setLowcoderCompsLoading } from "./redux/reduxActions/npmPluginActions";
+import { SimpleSubscriptionContextProvider } from "./util/context/SimpleSubscriptionContext";
 
 const LazyUserAuthComp = React.lazy(() => import("pages/userAuth"));
 const LazyInviteLanding = React.lazy(() => import("pages/common/inviteLanding"));
@@ -310,33 +311,35 @@ class AppIndex extends React.Component<AppIndexProps, any> {
                 component={LazyPublicAppEditor}
               />
 
-              <LazyRoute
-                fallback="layout"
-                path={APP_EDITOR_URL}
-                component={LazyAppEditor}
-              />
-              <LazyRoute
-                fallback="layout"
-                path={[
-                  USER_PROFILE_URL,
-                  NEWS_URL,
-                  ORG_HOME_URL,
-                  ALL_APPLICATIONS_URL,
-                  DATASOURCE_CREATE_URL,
-                  DATASOURCE_EDIT_URL,
-                  DATASOURCE_URL,
-                  SUPPORT_URL,
-                  QUERY_LIBRARY_URL,
-                  FOLDERS_URL,
-                  FOLDER_URL,
-                  TRASH_URL,
-                  SETTING_URL,
-                  MARKETPLACE_URL,
-                  ADMIN_APP_URL
-                ]}
-                // component={ApplicationListPage}
-                component={LazyApplicationHome}
-              />
+              <SimpleSubscriptionContextProvider>
+                <LazyRoute
+                  fallback="layout"
+                  path={APP_EDITOR_URL}
+                  component={LazyAppEditor}
+                />
+                <LazyRoute
+                  fallback="layout"
+                  path={[
+                    USER_PROFILE_URL,
+                    NEWS_URL,
+                    ORG_HOME_URL,
+                    ALL_APPLICATIONS_URL,
+                    DATASOURCE_CREATE_URL,
+                    DATASOURCE_EDIT_URL,
+                    DATASOURCE_URL,
+                    SUPPORT_URL,
+                    QUERY_LIBRARY_URL,
+                    FOLDERS_URL,
+                    FOLDER_URL,
+                    TRASH_URL,
+                    SETTING_URL,
+                    MARKETPLACE_URL,
+                    ADMIN_APP_URL
+                  ]}
+                  // component={ApplicationListPage}
+                  component={LazyApplicationHome}
+                />
+              </SimpleSubscriptionContextProvider>
               <LazyRoute exact path={ADMIN_AUTH_URL} component={LazyUserAuthComp} />
               <LazyRoute path={USER_AUTH_URL} component={LazyUserAuthComp} />
               <LazyRoute
diff --git a/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx b/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
index 3370788ac9..9f43bda672 100644
--- a/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
+++ b/client/packages/lowcoder/src/comps/controls/iconscoutControl.tsx
@@ -30,6 +30,8 @@ import LoadingOutlined from "@ant-design/icons/LoadingOutlined";
 import Badge from "antd/es/badge";
 import { CrownFilled } from "@ant-design/icons";
 import { SUBSCRIPTION_SETTING } from "@lowcoder-ee/constants/routesURL";
+import { useSimpleSubscriptionContext } from "@lowcoder-ee/util/context/SimpleSubscriptionContext";
+import { SubscriptionProductsEnum } from "@lowcoder-ee/constants/subscriptionConstants";
 
 const ButtonWrapper = styled.div`
   width: 100%;
@@ -227,6 +229,11 @@ export const IconPicker = (props: {
   const [ downloading, setDownloading ] = useState(false)
   const [ searchText, setSearchText ] = useState<string>('')
   const [ searchResults, setSearchResults ] = useState<Array<any>>([]);
+  const { subscriptions } = useSimpleSubscriptionContext();
+
+  const mediaPackSubscription = subscriptions.find(
+    sub => sub.product === SubscriptionProductsEnum.MEDIAPACKAGE && sub.status === 'active'
+  );
 
   const onChangeRef = useRef(props.onChange);
   onChangeRef.current = props.onChange;
@@ -319,7 +326,7 @@ export const IconPicker = (props: {
               onClick={() => {
                 // check if premium content then show subscription popup
                 // TODO: if user has subscription then skip this if block
-                if (icon.price !== 0) {
+                if (!mediaPackSubscription) {
                   CustomModal.confirm({
                     title: trans("iconScout.buySubscriptionTitle"),
                     content: trans("iconScout.buySubscriptionContent"),
diff --git a/client/packages/lowcoder/src/pages/ApplicationV2/index.tsx b/client/packages/lowcoder/src/pages/ApplicationV2/index.tsx
index 71c13d039e..5856a131d1 100644
--- a/client/packages/lowcoder/src/pages/ApplicationV2/index.tsx
+++ b/client/packages/lowcoder/src/pages/ApplicationV2/index.tsx
@@ -69,7 +69,6 @@ import { SubscriptionProductsEnum } from '@lowcoder-ee/constants/subscriptionCon
 import AppEditor from "../editor/AppEditor";
 import { fetchDeploymentIdAction } from "@lowcoder-ee/redux/reduxActions/configActions";
 import { getDeploymentId } from "@lowcoder-ee/redux/selectors/configSelectors";
-import { SimpleSubscriptionContextProvider } from '@lowcoder-ee/util/context/SimpleSubscriptionContext';
 import {LoadingBarHideTrigger} from "@lowcoder-ee/util/hideLoading";
 
 const TabLabel = styled.div`
@@ -154,171 +153,169 @@ export default function ApplicationHome() {
   return (
     <DivStyled>
       <LoadingBarHideTrigger />
-      <SimpleSubscriptionContextProvider>
-        <Layout
-          sections={[
-            {
-              items: [
-                {
-                  text: <TabLabel>{trans("home.profile")}</TabLabel>,
-                  routePath: USER_PROFILE_URL,
-                  routeComp: UserProfileView,
-                  icon: ({ selected, ...otherProps }) => selected ? <UserIcon {...otherProps} width={"24px"}/> : <UserIcon {...otherProps} width={"24px"}/>,
-                  mobileVisible: true,
-                },
-                {
-                  text: <TabLabel>{trans("home.news")}</TabLabel>,
-                  routePath: NEWS_URL,
-                  routeComp: NewsView,
-                  icon: ({ selected, ...otherProps }) => selected ? <NewsIcon {...otherProps} width={"24px"}/> : <NewsIcon {...otherProps} width={"24px"}/>,
-                  visible: ({ user }) => user.orgDev,
-                  style: { color: "red" },
-                  mobileVisible: false,
-                },
-                {
-                  text: <TabLabel>{trans("home.orgHome")}</TabLabel>,
-                  routePath: ORG_HOME_URL,
-                  routePathExact: false,
-                  routeComp: OrgView,
-                  icon: ({ selected, ...otherProps }) => selected ? <WorkspacesIcon {...otherProps} width={"24px"}/> : <WorkspacesIcon {...otherProps} width={"24px"}/>,
-                  visible: ({ user }) => !user.orgDev,
-                  mobileVisible: true,
-                },
-                {
-                  text: <TabLabel>{trans("home.marketplace")}</TabLabel>,
-                  routePath: MARKETPLACE_URL,
-                  routePathExact: false,
-                  routeComp: MarketplaceView,
-                  icon: ({ selected, ...otherProps }) => selected ? <MarketplaceIcon {...otherProps} width={"24px"}/> : <MarketplaceIcon {...otherProps} width={"24px"}/>,
-                  mobileVisible: false,
-                },
-              ]
-            },
+      <Layout
+        sections={[
+          {
+            items: [
+              {
+                text: <TabLabel>{trans("home.profile")}</TabLabel>,
+                routePath: USER_PROFILE_URL,
+                routeComp: UserProfileView,
+                icon: ({ selected, ...otherProps }) => selected ? <UserIcon {...otherProps} width={"24px"}/> : <UserIcon {...otherProps} width={"24px"}/>,
+                mobileVisible: true,
+              },
+              {
+                text: <TabLabel>{trans("home.news")}</TabLabel>,
+                routePath: NEWS_URL,
+                routeComp: NewsView,
+                icon: ({ selected, ...otherProps }) => selected ? <NewsIcon {...otherProps} width={"24px"}/> : <NewsIcon {...otherProps} width={"24px"}/>,
+                visible: ({ user }) => user.orgDev,
+                style: { color: "red" },
+                mobileVisible: false,
+              },
+              {
+                text: <TabLabel>{trans("home.orgHome")}</TabLabel>,
+                routePath: ORG_HOME_URL,
+                routePathExact: false,
+                routeComp: OrgView,
+                icon: ({ selected, ...otherProps }) => selected ? <WorkspacesIcon {...otherProps} width={"24px"}/> : <WorkspacesIcon {...otherProps} width={"24px"}/>,
+                visible: ({ user }) => !user.orgDev,
+                mobileVisible: true,
+              },
+              {
+                text: <TabLabel>{trans("home.marketplace")}</TabLabel>,
+                routePath: MARKETPLACE_URL,
+                routePathExact: false,
+                routeComp: MarketplaceView,
+                icon: ({ selected, ...otherProps }) => selected ? <MarketplaceIcon {...otherProps} width={"24px"}/> : <MarketplaceIcon {...otherProps} width={"24px"}/>,
+                mobileVisible: false,
+              },
+            ]
+          },
 
-            {
-              items: [
-                // {
-                //   text: <MoreFoldersWrapper>{trans("home.allFolders")}</MoreFoldersWrapper>,
-                //   routePath: FOLDERS_URL,
-                //   routeComp: RootFolderListView,
-                //   icon: ({ selected, ...otherProps }) => selected ? <FolderIcon {...otherProps} width={"24px"}/> : <FolderIcon {...otherProps} width={"24px"}/>,
-                // },
-                {
-                  text: <TabLabel>{trans("home.allApplications")}</TabLabel>,
-                  routePath: ALL_APPLICATIONS_URL,
-                  routeComp: HomeView,
-                  icon: ({ selected, ...otherProps }) => selected ? <AppsIcon {...otherProps} width={"24px"}/> : <AppsIcon {...otherProps} width={"24px"}/>,
-                  mobileVisible: true,
-                },
-              ],
-            },
-    
-            {
-              items: [
-                
-                {
-                  text: <TabLabel>{trans("home.queryLibrary")}</TabLabel>,
-                  routePath: QUERY_LIBRARY_URL,
-                  routeComp: QueryLibraryEditor,
-                  icon: ({ selected, ...otherProps }) => selected ? <HomeQueryLibraryIcon {...otherProps} width={"24px"}/> : <HomeQueryLibraryIcon {...otherProps} width={"24px"}/>,
-                  visible: ({ user }) => user.orgDev,
-                  mobileVisible: false,
-                },
-                {
-                  text: <TabLabel>{trans("home.datasource")}</TabLabel>,
-                  routePath: DATASOURCE_URL,
-                  routePathExact: false,
-                  routeComp: DatasourceHome,
-                  icon: ({ selected, ...otherProps }) => selected ? <HomeDataSourceIcon {...otherProps} width={"24px"}/> : <HomeDataSourceIcon {...otherProps} width={"24px"}/>,
-                  visible: ({ user }) => user.orgDev,
-                  onSelected: (_, currentPath) => currentPath.split("/")[1] === "datasource",
-                  mobileVisible: false,
-                },
-              ],
-            },
-            isEE() ? {
-              items: [
-                {
-                  text: <TabLabel>{trans("settings.AppUsage")}</TabLabel>,
-                  routePath: "/ee/6600ae8724a23f365ba2ed4c/admin",
-                  routePathExact: false,
-                  routeComp: AppEditor,
-                  icon: ({ selected, ...otherProps }) => selected ? ( <EnterpriseIcon {...otherProps} width={"24px"}/> ) : ( <EnterpriseIcon {...otherProps} width={"24px"}/> ),
-                  visible: ({ user }) => user.orgDev,
-                  mobileVisible: false,
-                },
-              ],
-            } : { items: [] },
+          {
+            items: [
+              // {
+              //   text: <MoreFoldersWrapper>{trans("home.allFolders")}</MoreFoldersWrapper>,
+              //   routePath: FOLDERS_URL,
+              //   routeComp: RootFolderListView,
+              //   icon: ({ selected, ...otherProps }) => selected ? <FolderIcon {...otherProps} width={"24px"}/> : <FolderIcon {...otherProps} width={"24px"}/>,
+              // },
+              {
+                text: <TabLabel>{trans("home.allApplications")}</TabLabel>,
+                routePath: ALL_APPLICATIONS_URL,
+                routeComp: HomeView,
+                icon: ({ selected, ...otherProps }) => selected ? <AppsIcon {...otherProps} width={"24px"}/> : <AppsIcon {...otherProps} width={"24px"}/>,
+                mobileVisible: true,
+              },
+            ],
+          },
+  
+          {
+            items: [
+              
+              {
+                text: <TabLabel>{trans("home.queryLibrary")}</TabLabel>,
+                routePath: QUERY_LIBRARY_URL,
+                routeComp: QueryLibraryEditor,
+                icon: ({ selected, ...otherProps }) => selected ? <HomeQueryLibraryIcon {...otherProps} width={"24px"}/> : <HomeQueryLibraryIcon {...otherProps} width={"24px"}/>,
+                visible: ({ user }) => user.orgDev,
+                mobileVisible: false,
+              },
+              {
+                text: <TabLabel>{trans("home.datasource")}</TabLabel>,
+                routePath: DATASOURCE_URL,
+                routePathExact: false,
+                routeComp: DatasourceHome,
+                icon: ({ selected, ...otherProps }) => selected ? <HomeDataSourceIcon {...otherProps} width={"24px"}/> : <HomeDataSourceIcon {...otherProps} width={"24px"}/>,
+                visible: ({ user }) => user.orgDev,
+                onSelected: (_, currentPath) => currentPath.split("/")[1] === "datasource",
+                mobileVisible: false,
+              },
+            ],
+          },
+          isEE() ? {
+            items: [
+              {
+                text: <TabLabel>{trans("settings.AppUsage")}</TabLabel>,
+                routePath: "/ee/6600ae8724a23f365ba2ed4c/admin",
+                routePathExact: false,
+                routeComp: AppEditor,
+                icon: ({ selected, ...otherProps }) => selected ? ( <EnterpriseIcon {...otherProps} width={"24px"}/> ) : ( <EnterpriseIcon {...otherProps} width={"24px"}/> ),
+                visible: ({ user }) => user.orgDev,
+                mobileVisible: false,
+              },
+            ],
+          } : { items: [] },
 
-            !supportSubscription && user.orgDev ? {
-              items: [
-                {
-                  text: <TabLabel>{trans("home.support")}</TabLabel>,
-                  routePath: SUBSCRIPTION_SETTING,
-                  routeComp: Subscription,
-                  routePathExact: false,
-                  icon: ({ selected, ...otherProps }) => selected ? <SupportIcon {...otherProps} width={"24px"}/> : <SupportIcon {...otherProps} width={"24px"}/>,
-                  mobileVisible: true,
-                },
-              ],
-            } : { items: [] },
+          !supportSubscription && user.orgDev ? {
+            items: [
+              {
+                text: <TabLabel>{trans("home.support")}</TabLabel>,
+                routePath: SUBSCRIPTION_SETTING,
+                routeComp: Subscription,
+                routePathExact: false,
+                icon: ({ selected, ...otherProps }) => selected ? <SupportIcon {...otherProps} width={"24px"}/> : <SupportIcon {...otherProps} width={"24px"}/>,
+                mobileVisible: true,
+              },
+            ],
+          } : { items: [] },
 
-            supportSubscription && user.orgDev ? {
-              items: [
-                {
-                  text: <TabLabel>{trans("home.support")}</TabLabel>,
-                  routePath: SUPPORT_URL,
-                  routeComp: Support,
-                  routePathExact: false,
-                  icon: ({ selected, ...otherProps }) => selected ? <SupportIcon {...otherProps} width={"24px"}/> : <SupportIcon {...otherProps} width={"24px"}/>,
-                  mobileVisible: true,
-                },
-              ],
-            } : { items: [] },
+          supportSubscription && user.orgDev ? {
+            items: [
+              {
+                text: <TabLabel>{trans("home.support")}</TabLabel>,
+                routePath: SUPPORT_URL,
+                routeComp: Support,
+                routePathExact: false,
+                icon: ({ selected, ...otherProps }) => selected ? <SupportIcon {...otherProps} width={"24px"}/> : <SupportIcon {...otherProps} width={"24px"}/>,
+                mobileVisible: true,
+              },
+            ],
+          } : { items: [] },
 
-            {
-              items: [
-                {
-                  text: <TabLabel>{trans("settings.title")}</TabLabel>,
-                  routePath: SETTING_URL,
-                  routePathExact: false,
-                  routeComp: Setting,
-                  icon: ({ selected, ...otherProps }) => selected ? <HomeSettingIcon {...otherProps} width={"24px"}/> : <HomeSettingIcon {...otherProps} width={"24px"}/>,
-                  visible: ({ user }) => user.orgDev,
-                  onSelected: (_, currentPath) => currentPath.split("/")[1] === "setting",
-                  mobileVisible: false,
-                }
-              ]
-            },
+          {
+            items: [
+              {
+                text: <TabLabel>{trans("settings.title")}</TabLabel>,
+                routePath: SETTING_URL,
+                routePathExact: false,
+                routeComp: Setting,
+                icon: ({ selected, ...otherProps }) => selected ? <HomeSettingIcon {...otherProps} width={"24px"}/> : <HomeSettingIcon {...otherProps} width={"24px"}/>,
+                visible: ({ user }) => user.orgDev,
+                onSelected: (_, currentPath) => currentPath.split("/")[1] === "setting",
+                mobileVisible: false,
+              }
+            ]
+          },
 
-            {
-              items: [
-                {
-                  text: <TabLabel>{trans("home.trash")}</TabLabel>,
-                  routePath: TRASH_URL,
-                  routeComp: TrashView,
-                  icon: ({ selected, ...otherProps }) => selected ? <RecyclerIcon {...otherProps} width={"24px"}/> : <RecyclerIcon {...otherProps} width={"24px"}/>,
-                  visible: ({ user }) => user.orgDev,
-                  mobileVisible: false,
-                },
-              ],
-            },
+          {
+            items: [
+              {
+                text: <TabLabel>{trans("home.trash")}</TabLabel>,
+                routePath: TRASH_URL,
+                routeComp: TrashView,
+                icon: ({ selected, ...otherProps }) => selected ? <RecyclerIcon {...otherProps} width={"24px"}/> : <RecyclerIcon {...otherProps} width={"24px"}/>,
+                visible: ({ user }) => user.orgDev,
+                mobileVisible: false,
+              },
+            ],
+          },
 
-            // this we need to show the Folders view in the Admin Area
-            {
-              items: [
-                {
-                  text: "",
-                  routePath: FOLDER_URL,
-                  routeComp: FolderView,
-                  visible: () => false,
-                }
-              ]
-            }
+          // this we need to show the Folders view in the Admin Area
+          {
+            items: [
+              {
+                text: "",
+                routePath: FOLDER_URL,
+                routeComp: FolderView,
+                visible: () => false,
+              }
+            ]
+          }
 
-          ]}
-        />
-      </SimpleSubscriptionContextProvider>
+        ]}
+      />
     </DivStyled>
   );
 }