157 lines
6.4 KiB
JavaScript
157 lines
6.4 KiB
JavaScript
import {React, Strings} from "modules";
|
|
|
|
import Screen from "../../structs/screen";
|
|
import CloseButton from "../icons/close";
|
|
import MaximizeIcon from "../icons/fullscreen";
|
|
import Modals from "../modals";
|
|
|
|
const {useState, useCallback, useEffect, useRef} = React;
|
|
|
|
|
|
function confirmClose(confirmationText) {
|
|
return new Promise(resolve => {
|
|
Modals.showConfirmationModal(Strings.Modals.confirmAction, confirmationText, {
|
|
danger: true,
|
|
confirmText: Strings.Modals.close,
|
|
onConfirm: () => {resolve(true);},
|
|
onCancel: () => {resolve(false);}
|
|
});
|
|
});
|
|
}
|
|
|
|
export default function FloatingWindow({id, title, resizable, children, className, center, top: initialTop, left: initialLeft, width: initialWidth, height: initialHeight, minX = 0, minY = 0, maxX = Screen.width, maxY = Screen.height, onResize, close: doClose, confirmClose: doConfirmClose, confirmationText}) {
|
|
const [modalOpen, setOpen] = useState(false);
|
|
const [isDragging, setDragging] = useState(false);
|
|
const [position, setPosition] = useState({x: center ? (Screen.width / 2) - (initialWidth / 2) : initialLeft, y: center ? (Screen.height / 2) - (initialHeight / 2) : initialTop});
|
|
const [offset, setOffset] = useState({x: 0, y: 0});
|
|
const [size, setSize] = useState({width: 0, height: 0});
|
|
|
|
const titlebar = useRef(null);
|
|
const window = useRef(null);
|
|
|
|
|
|
const onResizeStart = useCallback(() => {
|
|
setSize({width: window.current.offsetWidth, height: window.current.offsetHeight});
|
|
}, [window]);
|
|
|
|
|
|
const onDrag = useCallback((e) => {
|
|
if (!isDragging) return;
|
|
let newTop = (e.clientY - offset.y);
|
|
if (newTop <= minY) newTop = minY;
|
|
if (newTop + size.height >= maxY) newTop = maxY - size.height;
|
|
|
|
let newLeft = (e.clientX - offset.x);
|
|
if (newLeft <= minX) newLeft = minX;
|
|
if (newLeft + size.width >= maxX) newLeft = maxX - size.width;
|
|
|
|
setPosition({x: newLeft, y: newTop});
|
|
}, [offset, size, isDragging, minX, minY, maxX, maxY]);
|
|
|
|
|
|
const onDragStart = useCallback((e) => {
|
|
const div = window.current;
|
|
setOffset({x: e.clientX - parseInt(div.offsetLeft), y: e.clientY - parseInt(div.offsetTop)});
|
|
setDragging(true);
|
|
}, [window]);
|
|
|
|
|
|
const onDragStop = useCallback(() => {
|
|
setDragging(false);
|
|
const width = window.current.offsetWidth;
|
|
const height = window.current.offsetHeight;
|
|
if (width != size.width || height != size.height) {
|
|
if (onResize) onResize();
|
|
const left = parseInt(window.current.style.left);
|
|
const top = parseInt(window.current.style.top);
|
|
if (left + width >= maxX) window.current.style.width = (maxX - left) + "px";
|
|
if (top + height >= maxY) window.current.style.height = (maxY - top) + "px";
|
|
}
|
|
|
|
setSize({width, height});
|
|
}, [window, size, maxX, maxY, onResize]);
|
|
|
|
|
|
useEffect(() => {
|
|
const winRef = window.current;
|
|
const titleRef = titlebar.current;
|
|
winRef.addEventListener("mousedown", onResizeStart, false);
|
|
titleRef.addEventListener("mousedown", onDragStart, false);
|
|
document.addEventListener("mouseup", onDragStop, false);
|
|
document.addEventListener("mousemove", onDrag, true);
|
|
|
|
return () => {
|
|
document.removeEventListener("mouseup", onDragStop, false);
|
|
document.removeEventListener("mousemove", onDrag, true);
|
|
winRef.removeEventListener("mousedown", onResizeStart, false);
|
|
titleRef.removeEventListener("mousedown", onDragStart, false);
|
|
};
|
|
}, [titlebar, window, onDragStart, onDragStop, onDrag, onResizeStart]);
|
|
|
|
|
|
const maximize = useCallback(() => {
|
|
window.current.style.width = "100%";
|
|
window.current.style.height = "100%";
|
|
if (onResize) onResize();
|
|
|
|
const width = window.current.offsetWidth;
|
|
const height = window.current.offsetHeight;
|
|
const left = parseInt(window.current.style.left);
|
|
const top = parseInt(window.current.style.top);
|
|
|
|
const right = left + width;
|
|
const bottom = top + height;
|
|
|
|
// Prevent expanding off the bottom and right and readjust position
|
|
if (bottom > maxY) window.current.style.top = (maxY - height) + "px";
|
|
if (right > maxX) window.current.style.left = (maxX - width) + "px";
|
|
|
|
const newLeft = parseInt(window.current.style.left);
|
|
const newTop = parseInt(window.current.style.top);
|
|
|
|
// For small screens it's possible pushes us off the other direction... we need to readjust size
|
|
if (newTop < minY) {
|
|
const difference = minY - newTop;
|
|
window.current.style.top = minY + "px";
|
|
window.current.style.height = (height - difference) + "px";
|
|
}
|
|
if (newLeft < minX) {
|
|
const difference = minX - newLeft;
|
|
window.current.style.left = minX + "px";
|
|
window.current.style.height = (width - difference) + "px";
|
|
}
|
|
}, [window, minX, minY, maxX, maxY, onResize]);
|
|
|
|
|
|
const close = useCallback(async () => {
|
|
let shouldClose = true;
|
|
const didConfirmClose = typeof(doConfirmClose) == "function" ? doConfirmClose() : doConfirmClose;
|
|
if (didConfirmClose) {
|
|
setOpen(true);
|
|
shouldClose = await confirmClose(confirmationText);
|
|
setOpen(false);
|
|
}
|
|
if (doClose && shouldClose) doClose();
|
|
}, [confirmationText, doClose, doConfirmClose]);
|
|
|
|
|
|
|
|
const finalClassname = `floating-window${` ${className}` || ""}${resizable ? " resizable" : ""}${modalOpen ? " modal-open" : ""}`;
|
|
const styles = {height: initialHeight, width: initialWidth, left: position.x || 0, top: position.y || 0};
|
|
return <div id={id} className={finalClassname} ref={window} style={styles}>
|
|
<div className="floating-window-titlebar" ref={titlebar}>
|
|
<span className="title">{title}</span>
|
|
<div className="floating-window-buttons">
|
|
<div className="button maximize-button" onClick={maximize}>
|
|
<MaximizeIcon size="18px" />
|
|
</div>
|
|
<div className="button close-button" onClick={close}>
|
|
<CloseButton />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="floating-window-content">
|
|
{children}
|
|
</div>
|
|
</div>;
|
|
} |