Skip to content

Commit 1d2b53d

Browse files
added popover to show countdown and check editing status button
1 parent a6f4bd4 commit 1d2b53d

File tree

5 files changed

+89
-32
lines changed

5 files changed

+89
-32
lines changed

client/packages/lowcoder/src/pages/common/header.tsx

Lines changed: 79 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { default as Dropdown } from "antd/es/dropdown";
22
import { default as Skeleton } from "antd/es/skeleton";
33
import { default as Radio, RadioChangeEvent } from "antd/es/radio";
4+
import { default as Statistic} from "antd/es/statistic";
5+
import { default as Flex} from "antd/es/flex";
6+
import { default as Popover } from "antd/es/popover";
7+
import { default as Typography } from "antd/es/typography";
48
import LayoutHeader from "components/layout/Header";
59
import { SHARE_TITLE } from "constants/apiConstants";
610
import { AppTypeEnum } from "constants/applicationConstants";
@@ -20,12 +24,13 @@ import {
2024
Middle,
2125
ModuleIcon,
2226
PackUpIcon,
27+
RefreshIcon,
2328
Right,
2429
TacoButton,
2530
} from "lowcoder-design";
2631
import { trans } from "i18n";
2732
import dayjs from "dayjs";
28-
import { useContext, useEffect, useMemo, useState } from "react";
33+
import { useContext, useEffect, useMemo, useRef, useState } from "react";
2934
import { useDispatch, useSelector } from "react-redux";
3035
import {
3136
publishApplication,
@@ -58,7 +63,10 @@ import { LockOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
5863
import Avatar from 'antd/es/avatar';
5964
import UserApi from "@lowcoder-ee/api/userApi";
6065
import { validateResponse } from "@lowcoder-ee/api/apiUtils";
66+
import ProfileImage from "./profileImage";
6167

68+
const { Countdown } = Statistic;
69+
const { Text } = Typography;
6270

6371
const StyledLink = styled.a`
6472
display: flex;
@@ -186,6 +194,10 @@ const GrayBtn = styled(TacoButton)`
186194
color: #ffffff;
187195
border: none;
188196
}
197+
198+
&[disabled] {
199+
cursor: not-allowed;
200+
}
189201
}
190202
`;
191203

@@ -282,6 +294,23 @@ const WarningIcon = styled(ExclamationCircleOutlined)`
282294
color: #ff4d4f; /* Red color for the icon */
283295
`;
284296

297+
const StyledCountdown = styled(Countdown)`
298+
.ant-statistic-content {
299+
color: #ff4d4f;
300+
margin-top: 2px;
301+
text-align: center;
302+
}
303+
`;
304+
305+
const StyledRefreshIcon = styled(RefreshIcon)`
306+
width: 16px !important;
307+
height: 16px !important;
308+
margin-right: -3px !important;
309+
> g > g {
310+
stroke: white;
311+
}
312+
`;
313+
285314
// Add the lock icon logic for disabled options
286315
const DropdownMenuStyled = styled(DropdownMenu)`
287316
.ant-dropdown-menu-item:hover {
@@ -314,6 +343,8 @@ function HeaderProfile(props: { user: User }) {
314343
);
315344
}
316345

346+
const setCountdown = () => dayjs().add(3, 'minutes').toISOString();
347+
317348
export type PanelStatus = { left: boolean; bottom: boolean; right: boolean };
318349
export type TogglePanel = (panel?: keyof PanelStatus) => void;
319350

@@ -332,7 +363,7 @@ type HeaderProps = {
332363
// header in editor page
333364
export default function Header(props: HeaderProps) {
334365
const editorState = useContext(EditorContext);
335-
const { blockEditing } = useContext(ExternalEditorContext);
366+
const { blockEditing, fetchApplication } = useContext(ExternalEditorContext);
336367
const { togglePanel } = props;
337368
const { toggleEditorModeStatus } = props;
338369
const { left, bottom, right } = props.panelStatus;
@@ -347,6 +378,8 @@ export default function Header(props: HeaderProps) {
347378
const [editing, setEditing] = useState(false);
348379
const [permissionDialogVisible, setPermissionDialogVisible] = useState(false);
349380
const [editingUser, setEditingUser] = useState<CurrentUser>();
381+
const [enableCheckEditingStatus, setEnableCheckEditingStatus] = useState<boolean>(false);
382+
const editingCountdown = useRef(setCountdown());
350383

351384
const isModule = appType === AppTypeEnum.Module;
352385

@@ -434,8 +467,6 @@ export default function Header(props: HeaderProps) {
434467

435468
const headerMiddle = (
436469
<>
437-
<>
438-
</>
439470
<Radio.Group
440471
onChange={onEditorStateValueChange}
441472
value={props.editorModeStatus}
@@ -503,20 +534,54 @@ export default function Header(props: HeaderProps) {
503534
<>
504535
{/* Display a hint about who is editing the app */}
505536
{blockEditing && (
506-
<Tooltip
507-
title="Changes will not be saved while another user is editing this app."
508-
color="red"
509-
placement="bottom"
537+
<>
538+
<Popover
539+
style={{ width: 200 }}
540+
content={() => {
541+
return (
542+
<Flex vertical gap={10} align="center">
543+
<Text>
544+
Changes will not be saved while another <br/> user is editing this app.
545+
</Text>
546+
<StyledCountdown
547+
title="Editing Availability"
548+
value={editingCountdown.current}
549+
onFinish={() => {
550+
setEnableCheckEditingStatus(true)
551+
}}
552+
/>
553+
<Tooltip
554+
title="You will be able to check the editing status after the countdown."
555+
placement="bottom"
556+
>
557+
<TacoButton
558+
style={{width: '100%'}}
559+
buttonType="primary"
560+
disabled={blockEditing && !enableCheckEditingStatus}
561+
onClick={() => {
562+
fetchApplication?.();
563+
setEnableCheckEditingStatus(false);
564+
editingCountdown.current = setCountdown();
565+
}}
566+
>
567+
<StyledRefreshIcon />
568+
<span>Check Editing Status</span>
569+
</TacoButton>
570+
</Tooltip>
571+
</Flex>
572+
)
573+
}}
574+
trigger="hover"
510575
>
511576
<EditingNoticeWrapper>
512-
<Avatar size="small" src={user.avatarUrl} />
577+
<ProfileImage source={user.avatarUrl} userName={user.username} side={24} />
513578
<EditingHintText>
514-
{/* {`${user.username} is currently editing this app.`} */}
515-
{`${editingUser?.name || 'Someone'} is currently editing this app`}
579+
{`${editingUser?.name || 'Someone'} is editing this app`}
516580
</EditingHintText>
517581
<WarningIcon />
518582
</EditingNoticeWrapper>
519-
</Tooltip>
583+
</Popover>
584+
</>
520585
)}
521586

522587
{applicationId && (
@@ -529,7 +594,7 @@ export default function Header(props: HeaderProps) {
529594
/>
530595
)}
531596
{canManageApp(user, application) && (
532-
<GrayBtn onClick={() => setPermissionDialogVisible(true)}>
597+
<GrayBtn onClick={() => setPermissionDialogVisible(true)} disabled={blockEditing}>
533598
{SHARE_TITLE}
534599
</GrayBtn>
535600
)}
@@ -596,6 +661,7 @@ export default function Header(props: HeaderProps) {
596661
applicationId,
597662
permissionDialogVisible,
598663
blockEditing, // Include the state in the dependency array
664+
enableCheckEditingStatus,
599665
editingUser?.name,
600666
]);
601667

client/packages/lowcoder/src/pages/editor/AppEditor.tsx

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ const AppEditor = React.memo(() => {
7676
);
7777

7878
const firstRendered = useRef(false);
79-
const fetchInterval = useRef<number>(0);
8079
const orgId = useMemo(() => currentUser.currentOrgId, [currentUser.currentOrgId]);
8180
const [isDataSourcePluginRegistered, setIsDataSourcePluginRegistered] = useState(false);
8281
const [appError, setAppError] = useState('');
@@ -188,22 +187,6 @@ const AppEditor = React.memo(() => {
188187
fetchApplication();
189188
}, [fetchApplication]);
190189

191-
useEffect(() => {
192-
if(!blockEditing && fetchInterval.current) {
193-
notificationInstance.info({
194-
message: 'Editing Enabled',
195-
description: 'Editing is now enabled. You can proceed with your changes.'
196-
});
197-
return clearInterval(fetchInterval.current);
198-
}
199-
if(blockEditing) {
200-
fetchInterval.current = window.setInterval(() => {
201-
fetchApplication();
202-
}, 60000);
203-
}
204-
return () => clearInterval(fetchInterval.current);
205-
}, [blockEditing, fetchApplication]);
206-
207190
const fallbackUI = useMemo(() => (
208191
<Flex align="center" justify="center" vertical style={{
209192
height: '300px',
@@ -249,6 +232,7 @@ const AppEditor = React.memo(() => {
249232
!fetchOrgGroupsFinished || !isDataSourcePluginRegistered || isCommonSettingsFetching
250233
}
251234
compInstance={compInstance}
235+
fetchApplication={fetchApplication}
252236
/>
253237
</Suspense>
254238
)}

client/packages/lowcoder/src/pages/editor/appEditorInternal.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,14 @@ interface AppEditorInternalViewProps {
7979
appInfo: AppSummaryInfo;
8080
loading: boolean;
8181
compInstance: RootCompInstanceType;
82+
fetchApplication?: () => void;
8283
}
8384

8485
export const AppEditorInternalView = React.memo((props: AppEditorInternalViewProps) => {
8586
const isUserViewMode = useUserViewMode();
8687
const extraExternalEditorState = useSelector(getExternalEditorState);
8788
const dispatch = useDispatch();
88-
const { readOnly, blockEditing, appInfo, compInstance } = props;
89+
const { readOnly, blockEditing, appInfo, compInstance, fetchApplication } = props;
8990

9091
const [externalEditorState, setExternalEditorState] = useState<ExternalEditorContextState>({
9192
changeExternalState: (state: Partial<ExternalEditorContextState>) => {
@@ -104,6 +105,7 @@ export const AppEditorInternalView = React.memo((props: AppEditorInternalViewPro
104105
applicationId: appInfo.id,
105106
hideHeader: window.location.pathname.split("/")[3] === "admin",
106107
blockEditing,
108+
fetchApplication: fetchApplication,
107109
...extraExternalEditorState,
108110
}));
109111
}, [
@@ -112,6 +114,7 @@ export const AppEditorInternalView = React.memo((props: AppEditorInternalViewPro
112114
readOnly,
113115
appInfo.appType, appInfo.id,
114116
blockEditing,
117+
fetchApplication,
115118
]);
116119

117120
useEffect(() => {

client/packages/lowcoder/src/util/context/ExternalEditorContext.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ export interface ExternalEditorContextState {
4242
* whether to block editing if someone else is editing the app
4343
*/
4444
blockEditing?: boolean;
45+
/**
46+
* passing this function to refresh app from header
47+
*/
48+
fetchApplication?: () => void;
4549

4650
changeExternalState?: (state: Partial<ExternalEditorContextState>) => void;
4751
}

client/packages/lowcoder/src/util/editoryHistory.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export function useAppHistory(
4646
showCost("addHistory", () => addHistory(actions));
4747
});
4848
return history;
49-
}, [appId, compContainer, reduxDispatch, readOnly]);
49+
}, [appId, compContainer, reduxDispatch, readOnly, blockEditing]);
5050
}
5151

5252
/**

0 commit comments

Comments
 (0)