Added simple extra popover to keep other controls out of display (#2880)
* Added simple extra popover to keep other controls out of display * Removed extra change * Fixed part of tests * Added extra comments * Updated version & changelogmain
parent
5bd61a43a4
commit
55b20e197d
@ -0,0 +1,114 @@
|
||||
// Copyright (C) 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/// <reference types="resize-observer-browser" />
|
||||
|
||||
import { SmallDashOutlined } from '@ant-design/icons';
|
||||
import Popover from 'antd/lib/popover';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
const extraControlsContentClassName = 'cvat-extra-controls-control-content';
|
||||
|
||||
let onUpdateChildren: Function | null = null;
|
||||
export function ExtraControlsControl(): JSX.Element {
|
||||
const [hasChildren, setHasChildren] = useState(false);
|
||||
const [initialized, setInitialized] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!initialized) {
|
||||
setInitialized(true);
|
||||
}
|
||||
|
||||
window.document.body.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
|
||||
}, []);
|
||||
|
||||
onUpdateChildren = () => {
|
||||
const contentElement = window.document.getElementsByClassName(extraControlsContentClassName)[0];
|
||||
if (contentElement) {
|
||||
setHasChildren(contentElement.children.length > 0);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Popover
|
||||
defaultVisible // we must render it at least one between using
|
||||
trigger={initialized ? 'hover' : 'click'} // trigger='hover' allows to close the popover by body click
|
||||
placement='right'
|
||||
overlayStyle={{ display: initialized ? '' : 'none' }}
|
||||
content={<div className={extraControlsContentClassName} />}
|
||||
>
|
||||
<SmallDashOutlined
|
||||
style={{ visibility: hasChildren ? 'visible' : 'hidden' }}
|
||||
className='cvat-extra-controls-control'
|
||||
/>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ControlVisibilityObserver<P = {}>(
|
||||
ControlComponent: React.FunctionComponent<P>,
|
||||
): React.FunctionComponent<P> {
|
||||
let visibilityHeightThreshold = 0; // minimum value of height when element can be pushed to main panel
|
||||
|
||||
return (props: P): JSX.Element | null => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [visible, setVisible] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (ref && ref.current) {
|
||||
const wrapper = ref.current;
|
||||
const parentElement = ref.current.parentElement as HTMLElement;
|
||||
|
||||
const reservedHeight = 45; // for itself
|
||||
const observer = new ResizeObserver(() => {
|
||||
// when parent size was changed, check again can we put the control
|
||||
// into the side panel or not
|
||||
const availableHeight = parentElement.offsetHeight;
|
||||
setVisible(availableHeight - reservedHeight >= visibilityHeightThreshold);
|
||||
});
|
||||
|
||||
if (ref && ref.current) {
|
||||
const availableHeight = parentElement.offsetHeight;
|
||||
// when first mount, remember bottom coordinate which equal to minimum parent width
|
||||
// to put the control into side panel
|
||||
visibilityHeightThreshold = wrapper.offsetTop + wrapper.offsetHeight;
|
||||
// start observing parent size
|
||||
observer.observe(ref.current.parentElement as HTMLElement);
|
||||
// then put it to extra controls if parent height is not enought
|
||||
setVisible(availableHeight - reservedHeight >= visibilityHeightThreshold);
|
||||
}
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}
|
||||
|
||||
return () => {};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// when visibility changed, we notify extra content element because now its children changed
|
||||
if (onUpdateChildren) {
|
||||
onUpdateChildren();
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
if (!visible) {
|
||||
const extraControlsContent = window.document.getElementsByClassName(extraControlsContentClassName)[0];
|
||||
if (extraControlsContent) {
|
||||
return ReactDOM.createPortal(<ControlComponent {...props} />, extraControlsContent);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// first mount always to side panel
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<ControlComponent {...props} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
Loading…
Reference in New Issue