Skip to content

Commit c96bb7e

Browse files
author
FalkWolsky
committed
Rounding Up Marketplace and Multi-Icon Component
1 parent 1c44f3a commit c96bb7e

File tree

13 files changed

+368
-24
lines changed

13 files changed

+368
-24
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ const AppCategories = Object.keys(ApplicationCategoriesEnum).map(
172172
const value = ApplicationCategoriesEnum[cat as AppCategoriesEnumKey];
173173
return {
174174
label: value,
175-
value,
175+
value: cat
176176
}
177177
}
178178
)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,3 +144,4 @@ IconBasicComp = class extends IconBasicComp {
144144
export const IconComp = withExposingConfigs(IconBasicComp, [
145145
NameConfigHidden,
146146
]);
147+
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
2+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
3+
import { findIconDefinition, library } from '@fortawesome/fontawesome-svg-core';
4+
import { fas } from '@fortawesome/free-solid-svg-icons';
5+
import { far } from '@fortawesome/free-regular-svg-icons';
6+
import * as AntdIcons from '@ant-design/icons';
7+
8+
library.add(far,fas);
9+
10+
function parseIconIdentifier(identifier: string) {
11+
if (identifier.startsWith('/icon:antd/')) {
12+
let name = identifier.split('/')[2];
13+
return { type: 'antd', name };
14+
}
15+
else if (identifier.startsWith('/icon:solid/') || identifier.startsWith('/icon:regular/')) {
16+
const [style, name] = identifier.substring(6).split('/');
17+
return { type: 'fontAwesome', style, name };
18+
}
19+
else if (identifier.startsWith('data:image')) {
20+
return { type: 'base64', data: identifier, name: "" };
21+
}
22+
else if (identifier.startsWith('http')) {
23+
return { type: 'url', url: identifier, name: "" };
24+
}
25+
else {
26+
return { type: 'unknown', name: "" };
27+
}
28+
}
29+
30+
interface IconProps {
31+
identifier: string;
32+
width?: string;
33+
height?: string;
34+
style?: React.CSSProperties;
35+
}
36+
37+
const convertToCamelCase = (name: string) => {
38+
return name.replace(/(-\w)/g, (match) => match[1].toUpperCase());
39+
}
40+
41+
const appendStyleSuffix = (name: string) => {
42+
if (name.endsWith('outlined')) {
43+
return name.replace('outlined', 'Outlined');
44+
} else if (name.endsWith('filled')) {
45+
return name.replace('filled', 'Filled');
46+
} else if (name.endsWith('twotone')) {
47+
return name.replace('twotone', 'TwoTone');
48+
}
49+
return name;
50+
}
51+
52+
// Multi icon Display Component
53+
54+
const baseMultiIconDisplay: React.FC<IconProps> = ({ identifier, width = '24px', height = '24px', style }) => {
55+
56+
const iconData = parseIconIdentifier(identifier);
57+
58+
if (iconData.type === 'fontAwesome') {
59+
const prefix = iconData.style === 'solid' ? 'fas' : 'far'; // 'fas' for solid, 'far' for regular
60+
// Find the icon definition using prefix and iconName
61+
const iconLookup = findIconDefinition({ prefix: prefix as any, iconName: iconData.name as any });
62+
63+
if (!iconLookup) {
64+
console.error(`Icon ${iconData.name} with prefix ${prefix} not found`);
65+
return null;
66+
}
67+
return <FontAwesomeIcon icon={iconLookup} style={{ width, height, ...style }} />;
68+
}
69+
else if (iconData.type === 'antd') {
70+
let iconName = convertToCamelCase(iconData.name);
71+
iconName = appendStyleSuffix(iconName);
72+
iconName = iconName.charAt(0).toUpperCase() + iconName.slice(1);
73+
const AntdIcon = (AntdIcons as any)[iconName];
74+
if (!AntdIcon) {
75+
console.error(`ANTd Icon ${iconData.name} not found`);
76+
return null;
77+
}
78+
return <AntdIcon style={{ fontSize: width, ...style }} />;
79+
}
80+
else if (iconData.type === 'url' || iconData.type === 'base64') {
81+
return <img src={iconData.type === 'url' ? iconData.url : iconData.data} alt="icon" style={{ width, height, ...style }} />;
82+
}
83+
else {
84+
return null; // Unknown type
85+
}
86+
};
87+
88+
export const MultiIconDisplay = baseMultiIconDisplay;

client/packages/lowcoder/src/comps/comps/tableComp/tablePropertyView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ function ColumnPropertyView<T extends MultiBaseComp<TableChildrenType>>(props: {
272272
<ToolTipLabel title={trans("table.refreshButtonTooltip")}>
273273
<StyledRefreshIcon
274274
onClick={() => {
275-
console.log("comp", comp);
275+
// console.log("comp", comp);
276276
comp.dispatch(
277277
wrapChildAction(
278278
"columns",

client/packages/lowcoder/src/constants/applicationConstants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ export interface ApplicationMeta {
7979
creatorEmail?: string;
8080
title?: string;
8181
description?: string;
82-
icon?: string;
82+
image?: string;
8383
category?: ApplicationCategoriesEnum;
8484
showheader?: boolean;
8585
orgId: string;

client/packages/lowcoder/src/i18n/locales/en.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2266,6 +2266,7 @@ export const en = {
22662266
"module": "Module",
22672267
"trash": "Trash",
22682268
"marketplace": "Marketplace",
2269+
"allCategories": "All Categories",
22692270
"queryLibrary": "Query Library",
22702271
"datasource": "Data Sources",
22712272
"selectDatasourceType": "Select Data Source Type",

client/packages/lowcoder/src/i18n/locales/zh.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2210,6 +2210,7 @@ home: {
22102210
"errorMarketplaceApps": "获取市场应用程序错误",
22112211
"localMarketplaceTitle": "本地市场",
22122212
"globalMarketplaceTitle": "Lowcoder 市场",
2213+
"allCategories": "所有类别",
22132214
memberPermissionList: "成员权限:",
22142215
orgName: "{orgName}管理员",
22152216
addMember: "添加成员",

client/packages/lowcoder/src/pages/ApplicationV2/HomeCardView.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import styled from "styled-components";
22
import { HomeRes } from "./HomeLayout";
33
import { HomeResCard } from "./HomeResCard";
4+
import { MarketplaceResCard } from "./MarketplaceResCard";
45
import React, { useState } from "react";
56
import { MoveToFolderModal } from "./MoveToFolderModal";
67

78
const ApplicationCardsWrapper = styled.div`
89
display: grid;
910
grid-template-columns: repeat(auto-fill, minmax(408px, 1fr));
10-
grid-template-rows: repeat(auto-fill, min(68px, 100%));
11+
grid-template-rows: repeat(auto-fill, min(auto, 100%));
1112
grid-column-gap: 112px;
13+
grid-row-gap: 20px;
1214
margin: 48px 26px 80px;
1315
overflow: hidden;
1416
@media screen and (max-width: 500px) {
@@ -23,6 +25,8 @@ export function HomeCardView(props: { resources: HomeRes[] }) {
2325
return (
2426
<ApplicationCardsWrapper>
2527
{props.resources.map((res) => (
28+
res.isMarketplace ?
29+
<MarketplaceResCard key={res.id} res={res} /> :
2630
<HomeResCard key={res.id} res={res} onMove={setNeedMoveRes} />
2731
))}
2832
<MoveToFolderModal source={needMoveRes} onClose={() => setNeedMoveRes(undefined)} />

client/packages/lowcoder/src/pages/ApplicationV2/HomeLayout.tsx

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { checkIsMobile } from "util/commonUtils";
3535
import MarketplaceHeaderImage from "assets/images/marketplaceHeaderImage.jpg";
3636
import { Divider } from "antd";
3737
import { Margin } from "../setting/theme/styledComponents";
38+
import { ApplicationCategoriesEnum } from "constants/applicationConstants";
3839

3940
const Wrapper = styled.div`
4041
display: flex;
@@ -171,7 +172,7 @@ const FilterDropdown = styled(Select)`
171172

172173
const FilterMenuItem = styled.div`
173174
display: flex;
174-
align-items: center;
175+
align-items: left;
175176
height: 29px;
176177
width: 100%;
177178
`;
@@ -253,6 +254,10 @@ export interface HomeRes {
253254
key: string;
254255
id: string;
255256
name: string;
257+
title?: string;
258+
description?: string;
259+
category?: string;
260+
icon?: string;
256261
type: HomeResTypeEnum;
257262
creator: string;
258263
lastModifyTime: number;
@@ -276,20 +281,37 @@ export interface HomeLayoutProps {
276281
}
277282

278283
export function HomeLayout(props: HomeLayoutProps) {
284+
285+
279286
const { breadcrumb = [], elements = [], localMarketplaceApps = [], globalMarketplaceApps = [],mode } = props;
287+
288+
const categoryOptions = [
289+
{ label: <FilterMenuItem>{trans("home.allCategories")}</FilterMenuItem>, value: 'All' },
290+
...Object.entries(ApplicationCategoriesEnum).map(([key, value]) => ({
291+
label: (
292+
<FilterMenuItem>
293+
{value}
294+
</FilterMenuItem>
295+
),
296+
value: key,
297+
})),
298+
];
299+
280300
const user = useSelector(getUser);
281301
const isFetching = useSelector(isFetchingFolderElements);
282302
const isSelfHost = window.location.host !== 'app.lowcoder.cloud';
283-
const [filterBy, setFilterBy] = useState<HomeResKey>("All");
303+
const [typeFilter, setTypeFilter] = useState<HomeResKey>("All");
304+
const [categoryFilter, setCategoryFilter] = useState<ApplicationCategoriesEnum | "All">("All");
284305
const [searchValue, setSearchValue] = useState("");
285306
const [layout, setLayout] = useState<HomeLayoutType>(
286307
checkIsMobile(window.innerWidth) ? "card" : getHomeLayout()
287308
);
288309

310+
289311
useEffect(() => saveHomeLayout(layout), [layout]);
290312

291313
useEffect(() => {
292-
// remove collision status from localstorage
314+
// remove collision status from localstorage, as the next selected app may have another collision status
293315
removeCollisionStatus();
294316
}, []);
295317

@@ -300,6 +322,7 @@ export function HomeLayout(props: HomeLayoutProps) {
300322
}
301323

302324
var displayElements = elements;
325+
303326
if (mode === "marketplace" && isSelfHost) {
304327
const markedLocalApps = localMarketplaceApps.map(app => ({ ...app, isLocalMarketplace: true }));
305328
const markedGlobalApps = globalMarketplaceApps.map(app => ({ ...app, isLocalMarketplace: false }));
@@ -319,18 +342,27 @@ export function HomeLayout(props: HomeLayoutProps) {
319342
: true
320343
)
321344
.filter((e) => {
322-
if (HomeResTypeEnum[filterBy].valueOf() === HomeResTypeEnum.All) {
345+
if (HomeResTypeEnum[typeFilter].valueOf() === HomeResTypeEnum.All) {
323346
return true;
324347
}
325348
if (e.folder) {
326-
return HomeResTypeEnum[filterBy] === HomeResTypeEnum.Folder;
349+
return HomeResTypeEnum[typeFilter] === HomeResTypeEnum.Folder;
327350
} else {
328-
if (filterBy === "Navigation") {
351+
if (typeFilter === "Navigation") {
329352
return NavigationTypes.map((t) => t.valueOf()).includes(e.applicationType);
330353
}
331-
return HomeResTypeEnum[filterBy].valueOf() === e.applicationType;
354+
return HomeResTypeEnum[typeFilter].valueOf() === e.applicationType;
355+
}
356+
})
357+
.filter((e) => {
358+
// If "All" is selected, do not filter out any elements based on category
359+
if (categoryFilter === 'All' || !categoryFilter) {
360+
return true;
332361
}
362+
// Otherwise, filter elements based on the selected category
363+
return !e.folder && e.category === categoryFilter.toString();
333364
})
365+
334366
.map((e) =>
335367
e.folder
336368
? {
@@ -347,6 +379,10 @@ export function HomeLayout(props: HomeLayoutProps) {
347379
key: e.applicationId,
348380
id: e.applicationId,
349381
name: e.name,
382+
title: e.title,
383+
description: e.description,
384+
category: e.category,
385+
icon: e.image,
350386
type: HomeResTypeEnum[HomeResTypeEnum[e.applicationType] as HomeResKey],
351387
creator: e?.creatorEmail ?? e.createBy,
352388
lastModifyTime: e.lastModifyTime,
@@ -385,6 +421,14 @@ export function HomeLayout(props: HomeLayoutProps) {
385421
}))
386422
]
387423

424+
const testOptions = [
425+
getFilterMenuItem(HomeResTypeEnum.All),
426+
getFilterMenuItem(HomeResTypeEnum.Application),
427+
getFilterMenuItem(HomeResTypeEnum.Module),
428+
...(mode !== "marketplace" ? [getFilterMenuItem(HomeResTypeEnum.Navigation)] : []),
429+
...(mode !== "trash" && mode !== "marketplace" ? [getFilterMenuItem(HomeResTypeEnum.Folder)] : []),
430+
];
431+
388432
return (
389433
<Wrapper>
390434
<HeaderWrapper>
@@ -414,19 +458,27 @@ export function HomeLayout(props: HomeLayoutProps) {
414458
{mode !== "folders" && mode !== "module" && (
415459
<FilterDropdown
416460
variant="borderless"
417-
value={filterBy}
418-
onChange={(value: any) => setFilterBy(value as HomeResKey)}
461+
value={typeFilter}
462+
onChange={(value: any) => setTypeFilter(value as HomeResKey)}
419463
options={[
420464
getFilterMenuItem(HomeResTypeEnum.All),
421465
getFilterMenuItem(HomeResTypeEnum.Application),
422466
getFilterMenuItem(HomeResTypeEnum.Module),
423467
...(mode !== "marketplace" ? [getFilterMenuItem(HomeResTypeEnum.Navigation)] : []),
424468
...(mode !== "trash" && mode !== "marketplace" ? [getFilterMenuItem(HomeResTypeEnum.Folder)] : []),
425-
426469
]}
427470
getPopupContainer={(node: any) => node}
428-
suffixIcon={<ArrowSolidIcon />}
429-
/>
471+
suffixIcon={<ArrowSolidIcon />} />
472+
)}
473+
{mode === "marketplace" && (
474+
<FilterDropdown
475+
style={{ minWidth: "220px" }}
476+
variant="borderless"
477+
value={categoryFilter}
478+
onChange={(value: any) => setCategoryFilter(value as ApplicationCategoriesEnum)}
479+
options={categoryOptions}
480+
// getPopupContainer={(node) => node}
481+
suffixIcon={<ArrowSolidIcon />} />
430482
)}
431483

432484
<OperationRightWrapper>

client/packages/lowcoder/src/pages/ApplicationV2/HomeResCard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ const Card = styled.div`
7373
align-items: center;
7474
height: 100%;
7575
width: 100%;
76-
border-bottom: 1px solid #f5f5f6;
76+
7777
padding: 0 10px;
7878
7979
button {

0 commit comments

Comments
 (0)