Skip to content

Commit a585d9e

Browse files
committed
Updates the Card UI on homepage
1 parent 077842b commit a585d9e

File tree

6 files changed

+267
-95
lines changed

6 files changed

+267
-95
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export interface ApplicationMeta {
8181
title?: string;
8282
description?: string;
8383
image?: string;
84+
icon?: string;
8485
category?: ApplicationCategoriesEnum;
8586
showheader?: boolean;
8687
orgId: string;

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3931,6 +3931,10 @@ export const en = {
39313931
"datasource": "Data Sources",
39323932
"selectDatasourceType": "Select Data Source Type",
39333933
"home": "Home",
3934+
"desc": "Description",
3935+
"renameApp": "Rename app",
3936+
"updateAppName": "Update Application Name",
3937+
"titleUpdateWarning": "Application name will not appear on the card",
39343938
"all": "All",
39353939
"app": "App",
39363940
"navigation": "Navigation",

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,7 @@ export function HomeLayout(props: HomeLayoutProps) {
469469
title: e.title,
470470
description: e.description,
471471
category: e.category,
472-
icon: e.image,
472+
icon: e.icon,
473473
type: HomeResTypeEnum[HomeResTypeEnum[e.applicationType] as HomeResKey],
474474
creator: e?.creatorEmail ?? e.createBy,
475475
lastModifyTime: e.lastModifyTime,
@@ -630,7 +630,7 @@ export function HomeLayout(props: HomeLayoutProps) {
630630

631631
<Divider />
632632

633-
<ContentWrapper>
633+
<ContentWrapper>
634634

635635
{isFetching && resList.length === 0 ? (
636636
<SkeletonStyle active paragraph={{ rows: 8, width: 648 }} title={false} />

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

Lines changed: 202 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { TacoButton } from "lowcoder-design/src/components/button"
2-
import { ReactNode, useState } from "react";
1+
import { TacoButton, CustomModal, Alert } from "lowcoder-design"
2+
import { useState, useEffect } from "react";
33
import { useDispatch } from "react-redux";
44
import { updateAppMetaAction } from "redux/reduxActions/applicationActions";
55
import styled from "styled-components";
@@ -25,6 +25,11 @@ import { useParams } from "react-router-dom";
2525
import { messageInstance } from "lowcoder-design/src/components/GlobalInstances";
2626
import {FolderIcon} from "icons";
2727
import { BrandedIcon } from "@lowcoder-ee/components/BrandedIcon";
28+
import { Typography } from "antd";
29+
import { default as Form } from "antd/es/form";
30+
import { default as Input } from "antd/es/input";
31+
import { MultiIconDisplay } from "@lowcoder-ee/comps/comps/multiIconDisplay";
32+
import { FormStyled } from "../setting/idSource/styledComponents";
2833

2934
const ExecButton = styled(TacoButton)`
3035
width: 52px;
@@ -50,14 +55,16 @@ const ExecButton = styled(TacoButton)`
5055
`;
5156

5257
const Wrapper = styled.div`
53-
height: 67px;
5458
padding: 0 6px;
5559
border-radius: 8px;
56-
margin-bottom: -1px;
57-
margin-top: 1px;
58-
60+
margin-bottom: 2px;
61+
margin-top: 2px;
62+
padding-top: 10px;
63+
padding-bottom: 10px;
64+
background-color: #fcfcfc;
65+
min-height: 100px;
5966
&:hover {
60-
background-color: #f5f7fa;
67+
background-color: #f5f5f6
6168
}
6269
`;
6370

@@ -98,7 +105,6 @@ const CardInfo = styled.div`
98105
height: 100%;
99106
flex-grow: 1;
100107
cursor: pointer;
101-
overflow: hidden;
102108
padding-right: 12px;
103109
104110
&:hover {
@@ -124,6 +130,7 @@ const AppTimeOwnerInfoLabel = styled.div`
124130
const OperationWrapper = styled.div`
125131
display: flex;
126132
align-items: center;
133+
padding-right: 10px;
127134
@media screen and (max-width: 500px) {
128135
> svg {
129136
display: none;
@@ -133,9 +140,75 @@ const OperationWrapper = styled.div`
133140

134141
const MONTH_MILLIS = 30 * 24 * 60 * 60 * 1000;
135142

143+
interface UpdateAppModalProps {
144+
visible: boolean;
145+
onCancel: () => void;
146+
onOk: (values: any) => void;
147+
res: HomeRes;
148+
folderId?: string;
149+
}
150+
151+
export function UpdateAppModal({ visible, onCancel, onOk, res, folderId }: UpdateAppModalProps) {
152+
const [detailsForm] = Form.useForm();
153+
154+
// Reset form values when res changes
155+
useEffect(() => {
156+
if (res && visible) {
157+
detailsForm.setFieldsValue({
158+
appName: res.name,
159+
title: res.title
160+
});
161+
}
162+
}, [res, visible, detailsForm]);
163+
164+
return (
165+
<CustomModal
166+
title={trans("home.updateAppName")}
167+
open={visible}
168+
destroyOnHidden
169+
onCancel={onCancel}
170+
showCancelButton={false}
171+
showOkButton
172+
width="440px"
173+
okText={trans("finish")}
174+
onOk={() => {
175+
detailsForm.validateFields().then((values) => {
176+
onOk(values);
177+
}).catch((errorInfo) => {
178+
console.error('Validation failed:', errorInfo);
179+
});
180+
}}
181+
>
182+
<FormStyled
183+
form={detailsForm}
184+
name="general"
185+
layout="vertical"
186+
style={{ maxWidth: '100%' }}
187+
autoComplete="off"
188+
>
189+
{res.title &&
190+
<Alert label={trans("home.titleUpdateWarning")} type="warning" />}
191+
<br/>
192+
193+
<Form.Item label={trans("home.name")} name="appName">
194+
<Input/>
195+
</Form.Item>
196+
197+
{res.title && (
198+
<Form.Item label={trans("title")} name="title">
199+
<Input disabled />
200+
</Form.Item>
201+
)}
202+
203+
</FormStyled>
204+
</CustomModal>
205+
);
206+
}
207+
136208
export function HomeResCard(props: { res: HomeRes; onMove: (res: HomeRes) => void; setModify:any; modify: boolean }) {
137209
const { res, onMove, setModify, modify } = props;
138210
const [appNameEditing, setAppNameEditing] = useState(false);
211+
const [dialogVisible, setDialogVisible] = useState(false)
139212
const dispatch = useDispatch();
140213

141214
const { folderId } = useParams<{ folderId: string }>();
@@ -161,96 +234,137 @@ export function HomeResCard(props: { res: HomeRes; onMove: (res: HomeRes) => voi
161234
else if (res.type === HomeResTypeEnum.NavLayout || res.type === HomeResTypeEnum.MobileTabLayout) {
162235
iconColor = "#af41ff";
163236
}
164-
165237
const Icon = resInfo.icon;
166238

239+
const handleModalOk = (values: any) => {
240+
dispatch(
241+
updateAppMetaAction({ applicationId: res.id, name: values.appName || res.name, folderId: folderId })
242+
);
243+
244+
setDialogVisible(false);
245+
setTimeout(() => {
246+
setModify(!modify);
247+
}, 200);
248+
};
249+
167250
return (
168-
<Wrapper>
169-
<Card>
170-
{Icon && (
171-
<BrandedIcon>
172-
<Icon width={"42px"} height={"42px"} style={
173-
{
174-
color: iconColor,
175-
marginRight: "10px",
176-
flexShrink: 0
177-
}
178-
} />
179-
</BrandedIcon>
180-
)}
181-
<CardInfo
182-
onClick={(e) => {
183-
if (appNameEditing) {
184-
return;
185-
}
186-
if (res.type === HomeResTypeEnum.Folder) {
187-
handleFolderViewClick(res.id);
188-
} else {
189-
if (checkIsMobile(window.innerWidth)) {
190-
history.push(APPLICATION_VIEW_URL(res.id, "view"));
191-
return;
192-
}
193-
if(res.isMarketplace) {
194-
handleMarketplaceAppViewClick(res.id);
195-
return;
196-
}
197-
res.isEditable ? handleAppEditClick(e, res.id) : handleAppViewClick(res.id);
198-
}
199-
}}
200-
>
201-
<TypographyText
202-
value={res.name}
203-
editing={appNameEditing}
204-
onChange={(value) => {
205-
if (!value.trim()) {
206-
messageInstance.warning(trans("home.nameCheckMessage"));
251+
<>
252+
<UpdateAppModal
253+
visible={dialogVisible}
254+
onCancel={() => setDialogVisible(false)}
255+
onOk={handleModalOk}
256+
res={res}
257+
folderId={folderId}
258+
/>
259+
260+
<Wrapper>
261+
<Card>
262+
{res.icon ?
263+
<MultiIconDisplay
264+
identifier={res.icon && typeof res.icon === 'string' ? res.icon : '/icon:antd/appstoreoutlined'}
265+
width="30px"
266+
height="30px"
267+
style={{
268+
marginRight: "6px",
269+
flexShrink: 0,
270+
color: "#b766db"
271+
}}
272+
/> :
273+
Icon && (
274+
<BrandedIcon>
275+
<Icon width={"42px"} height={"42px"} style={
276+
{
277+
color: iconColor,
278+
marginRight: "10px",
279+
flexShrink: 0
280+
}
281+
} />
282+
</BrandedIcon>
283+
)
284+
}
285+
<CardInfo
286+
onClick={(e) => {
287+
if (appNameEditing) {
207288
return;
208289
}
209290
if (res.type === HomeResTypeEnum.Folder) {
210-
dispatch(updateFolder({ id: res.id, name: value }));
211-
setTimeout(() => {
212-
setModify(!modify);
213-
}, 200);
291+
handleFolderViewClick(res.id);
214292
} else {
215-
dispatch(
216-
updateAppMetaAction({ applicationId: res.id, name: value, folderId: folderId })
217-
);
218-
setTimeout(() => {
219-
setModify(!modify);
220-
}, 200);
293+
if (checkIsMobile(window.innerWidth)) {
294+
history.push(APPLICATION_VIEW_URL(res.id, "view"));
295+
return;
296+
}
297+
if(res.isMarketplace) {
298+
handleMarketplaceAppViewClick(res.id);
299+
return;
300+
}
301+
res.isEditable ? handleAppEditClick(e, res.id) : handleAppViewClick(res.id);
221302
}
222-
setAppNameEditing(false);
223303
}}
224-
/>
225-
<AppTimeOwnerInfoLabel title={subTitle}>{subTitle}</AppTimeOwnerInfoLabel>
226-
</CardInfo>
227-
<OperationWrapper>
228-
{/* {res.isEditable && (
229-
<EditButton onClick={(e) => handleAppEditClick(e, res.id)} buttonType="primary">
230-
{trans("edit")}
231-
</EditButton>
232-
)} */}
233-
<ExecButton
234-
onClick={() =>
235-
res.type === HomeResTypeEnum.Folder
236-
? handleFolderViewClick(res.id)
237-
: res.isMarketplace
238-
? handleMarketplaceAppViewClick(res.id)
239-
: handleAppViewClick(res.id)
240-
}
241304
>
242-
{trans("view")}
243-
</ExecButton>
244-
<HomeResOptions
245-
res={res}
246-
onRename={() => setAppNameEditing(true)}
247-
onMove={(res) => onMove(res)}
248-
setModify={setModify}
249-
modify={modify}
250-
/>
251-
</OperationWrapper>
252-
</Card>
253-
</Wrapper>
305+
<TypographyText
306+
value={res.title || res.name}
307+
editing={false}
308+
onChange={(value) => {
309+
if (!value.trim()) {
310+
messageInstance.warning(trans("home.nameCheckMessage"));
311+
return;
312+
}
313+
if (res.type === HomeResTypeEnum.Folder) {
314+
dispatch(updateFolder({ id: res.id, name: value }));
315+
setTimeout(() => {
316+
setModify(!modify);
317+
}, 200);
318+
} else {
319+
dispatch(
320+
updateAppMetaAction({ applicationId: res.id, name: value, folderId: folderId })
321+
);
322+
setTimeout(() => {
323+
setModify(!modify);
324+
}, 200);
325+
}
326+
setAppNameEditing(false);
327+
}}
328+
/>
329+
330+
{res?.description
331+
&& <Typography.Text
332+
type="secondary"
333+
style={{ fontSize: 12, textWrap: "wrap"}}
334+
>
335+
{res?.description}
336+
</Typography.Text>}
337+
338+
<AppTimeOwnerInfoLabel title={subTitle}>{subTitle}</AppTimeOwnerInfoLabel>
339+
</CardInfo>
340+
<OperationWrapper>
341+
{/* {res.isEditable && (
342+
<EditButton onClick={(e) => handleAppEditClick(e, res.id)} buttonType="primary">
343+
{trans("edit")}
344+
</EditButton>
345+
)} */}
346+
<ExecButton
347+
onClick={() =>
348+
res.type === HomeResTypeEnum.Folder
349+
? handleFolderViewClick(res.id)
350+
: res.isMarketplace
351+
? handleMarketplaceAppViewClick(res.id)
352+
: handleAppViewClick(res.id)
353+
}
354+
>
355+
{trans("view")}
356+
</ExecButton>
357+
<HomeResOptions
358+
res={res}
359+
onRename={() => setDialogVisible(true)}
360+
onMove={(res) => onMove(res)}
361+
setModify={setModify}
362+
modify={modify}
363+
/>
364+
</OperationWrapper>
365+
</Card>
366+
</Wrapper>
367+
</>
254368
);
255369
}
256370

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export const HomeResOptions = (props: {
5353
if (res.isEditable) {
5454
options = [
5555
...options,
56-
{ text: trans("rename"), onClick: () => onRename(res) },
56+
{ text: trans("home.renameApp"), onClick: () => onRename(res) },
5757
{
5858
text: trans("header.duplicate", { type: HomeResInfo[res.type].name.toLowerCase() }),
5959
onClick: () => {

0 commit comments

Comments
 (0)