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)
pFad - Phonifier reborn

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

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


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy