11import { default as Dropdown } from "antd/es/dropdown" ;
22import { default as Skeleton } from "antd/es/skeleton" ;
33import { 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" ;
48import LayoutHeader from "components/layout/Header" ;
59import { SHARE_TITLE } from "constants/apiConstants" ;
610import { 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" ;
2631import { trans } from "i18n" ;
2732import dayjs from "dayjs" ;
28- import { useContext , useEffect , useMemo , useState } from "react" ;
33+ import { useContext , useEffect , useMemo , useRef , useState } from "react" ;
2934import { useDispatch , useSelector } from "react-redux" ;
3035import {
3136 publishApplication ,
@@ -58,7 +63,10 @@ import { LockOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
5863import Avatar from 'antd/es/avatar' ;
5964import UserApi from "@lowcoder-ee/api/userApi" ;
6065import { validateResponse } from "@lowcoder-ee/api/apiUtils" ;
66+ import ProfileImage from "./profileImage" ;
6167
68+ const { Countdown } = Statistic ;
69+ const { Text } = Typography ;
6270
6371const 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
286315const 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+
317348export type PanelStatus = { left : boolean ; bottom : boolean ; right : boolean } ;
318349export type TogglePanel = ( panel ?: keyof PanelStatus ) => void ;
319350
@@ -332,7 +363,7 @@ type HeaderProps = {
332363// header in editor page
333364export 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
0 commit comments