Convert floating windows

This commit is contained in:
Zack Rauen 2023-03-08 20:07:36 -05:00
parent 7d71d4f450
commit 99aa7ac1a8
4 changed files with 158 additions and 192 deletions

View File

@ -8,6 +8,7 @@ import ThemeManager from "./thememanager";
import Settings from "./settingsmanager";
import * as Builtins from "builtins";
import Modals from "../ui/modals";
import FloatingWindows from "../ui/floatingwindows";
import DataStore from "./datastore";
import DiscordModules from "./discordmodules";
import LoadingIcon from "../loadingicon";
@ -47,6 +48,7 @@ export default new class Core {
await Editor.initialize();
Modals.initialize();
FloatingWindows.initialize();
Logger.log("Startup", "Initializing Builtins");
for (const module in Builtins) {

View File

@ -1,54 +1,36 @@
import {React} from "modules";
import {React, Events} from "modules";
import FloatingWindow from "./window";
class FloatingWindowContainer extends React.Component {
const {useState, useCallback, useEffect} = React;
constructor(props) {
super(props);
this.state = {windows: []};
}
get minY() {
const appContainer = document.querySelector(`#app-mount > div[class*="app-"]`);
if (appContainer) return appContainer.offsetTop;
return 0;
}
render() {
return this.state.windows.map(window =>
<FloatingWindow {...window} close={this.close.bind(this, window.id)} minY={this.minY} key={window.id}>
{window.children}
</FloatingWindow>
);
}
open(window) {
this.setState(state => {
state.windows.push(window);
return {windows: state.windows};
});
}
close(id) {
this.setState(state => {
return {
windows: state.windows.filter(w => {
if (w.id == id && w.onClose) w.onClose();
return w.id != id;
})
};
});
}
static get id() {return "floating-windows";}
static get root() {
if (this._root) return this._root;
const container = document.createElement("div");
container.id = this.id;
document.body.append(container);
return this._root = container;
}
function minY() {
const appContainer = document.querySelector(`#app-mount > div[class*="app-"]`);
if (appContainer) return appContainer.offsetTop;
return 0;
}
export default FloatingWindowContainer;
export default function FloatingWindowContainer() {
useEffect(() => {
Events.on("open-window", open);
return () => Events.off("open-window", open);
}, []);
const [windows, setWindows] = useState([]);
const open = useCallback(window => {
setWindows([...windows, window]);
}, [windows]);
const close = useCallback(id => {
setWindows(windows.filter(w => {
if (w.id === id && w.onClose) w.onClose();
return w.id !== id;
}));
}, [windows]);
return windows.map(window =>
<FloatingWindow {...window} close={() => close(window.id)} minY={minY()} key={window.id}>
{window.children}
</FloatingWindow>
);
}

View File

@ -5,174 +5,156 @@ import CloseButton from "../icons/close";
import MaximizeIcon from "../icons/fullscreen";
import Modals from "../modals";
// const Draggable = WebpackModules.getByDisplayName("Draggable");
// {
// "dragAnywhere": true,
// "className": "pictureInPictureWindow-1B5qSe",
// "maxX": 1969,
// "maxY": this.maxY,
// "onDragStart": "ƒ () {}",
// "onDrag": "ƒ () {}",
// "onDragEnd": "ƒ () {}",
// "children": "<div />",
// "initialX": 0,
// "initialY": 0
// }
const {useState, useCallback, useEffect, useRef, useMemo} = React;
export default class FloatingWindow extends React.Component {
constructor(props) {
super(props);
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);}
});
});
}
this.state = {modalOpen: false};
export default function FloatingWindow(props) {
const [modalOpen, setOpen] = useState(false);
const [isDragging, setDragging] = useState(false);
const [position, setPosition] = useState({x: props.center ? (Screen.width / 2) - (props.width / 2) : props.left, y: props.center ? (Screen.height / 2) - (props.height / 2) : props.top});
const [offset, setOffset] = useState({x: 0, y: 0});
const [size, setSize] = useState({width: 0, height: 0});
this.offX = 0;
this.offY = 0;
const minX = useMemo(() => props.minX || 0);
const maxX = useMemo(() => props.maxX || Screen.width);
const minY = useMemo(() => props.minY || 0);
const maxY = useMemo(() => props.maxY || Screen.height);
this.maxX = this.props.maxX || Screen.width;
this.maxY = this.props.maxY || Screen.height;
this.minX = this.props.minX || 0;
this.minY = this.props.minY || 0;
const titlebar = useRef(null);
const window = useRef(null);
this.titlebar = React.createRef();
this.window = React.createRef();
this.close = this.close.bind(this);
this.maximize = this.maximize.bind(this);
this.onDrag = this.onDrag.bind(this);
this.onDragStart = this.onDragStart.bind(this);
this.onDragStop = this.onDragStop.bind(this);
this.onResizeStart = this.onResizeStart.bind(this);
}
const onResizeStart = useCallback(() => {
setSize({width: window.current.offsetWidth, height: window.current.offsetHeight});
}, [window]);
componentDidMount() {
this.window.current.addEventListener("mousedown", this.onResizeStart, false);
this.titlebar.current.addEventListener("mousedown", this.onDragStart, false);
document.addEventListener("mouseup", this.onDragStop, false);
}
onResizeStart() {
this.currentWidth = this.window.current.offsetWidth;
this.currentHeight = this.window.current.offsetHeight;
}
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;
onDragStop() {
document.removeEventListener("mousemove", this.onDrag, true);
const width = this.window.current.offsetWidth;
const height = this.window.current.offsetHeight;
if (width != this.currentWidth || height != this.currentHeight) {
if (this.props.onResize) this.props.onResize();
const left = parseInt(this.window.current.style.left);
const top = parseInt(this.window.current.style.top);
if (left + width >= this.maxX) this.window.current.style.width = (this.maxX - left) + "px";
if (top + height >= this.maxY) this.window.current.style.height = (this.maxY - top) + "px";
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});
}, [window, offset, size, isDragging]);
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 (props.onResize) props.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";
}
this.currentWidth = width;
this.currentHeight = height;
}
onDragStart(e) {
const div = this.window.current;
this.offY = e.clientY - parseInt(div.offsetTop);
this.offX = e.clientX - parseInt(div.offsetLeft);
document.addEventListener("mousemove", this.onDrag, true);
}
setSize({width, height});
}, [window, size, onDrag]);
onDrag(e) {
const div = this.window.current;
let newTop = (e.clientY - this.offY);
if (newTop <= this.minY) newTop = this.minY;
if (newTop + this.currentHeight >= this.maxY) newTop = this.maxY - this.currentHeight;
let newLeft = (e.clientX - this.offX);
if (newLeft <= this.minX) newLeft = this.minX;
if (newLeft + this.currentWidth >= this.maxX) newLeft = this.maxX - this.currentWidth;
useEffect(() => {
window.current.addEventListener("mousedown", onResizeStart, false);
titlebar.current.addEventListener("mousedown", onDragStart, false);
document.addEventListener("mouseup", onDragStop, false);
document.addEventListener("mousemove", onDrag, true);
div.style.top = newTop + "px";
div.style.left = newLeft + "px";
}
return () => {
document.removeEventListener("mouseup", onDragStop, false);
document.removeEventListener("mousemove", onDrag, true);
window?.current?.removeEventListener("mousedown", onResizeStart, false);
titlebar?.current?.removeEventListener("mousedown", onDragStart, false);
};
}, [titlebar, window, onDragStart, onDragStop, onDrag, onResizeStart]);
componentWillUnmount() {
this.titlebar.current.removeEventListener("mousedown", this.onDragStart, false);
document.removeEventListener("mouseup", this.onDragStop, false);
}
render() {
const top = this.props.center ? (Screen.height / 2) - (this.props.height / 2) : this.props.top;
const left = this.props.center ? (Screen.width / 2) - (this.props.width / 2) : this.props.left;
// console.log(top, left);
const className = `floating-window${` ${this.props.className}` || ""}${this.props.resizable ? " resizable" : ""}${this.state.modalOpen ? " modal-open" : ""}`;
const styles = {height: this.props.height, width: this.props.width, left: left || 0, top: top || 0};
return <div id={this.props.id} className={className} ref={this.window} style={styles}>
<div className="floating-window-titlebar" ref={this.titlebar}>
<span className="title">{this.props.title}</span>
<div className="floating-window-buttons">
<div className="button maximize-button" onClick={this.maximize}>
<MaximizeIcon size="18px" />
</div>
<div className="button close-button" onClick={this.close}>
<CloseButton />
</div>
</div>
</div>
<div className="floating-window-content">
{this.props.children}
</div>
</div>;
}
const maximize = useCallback(() => {
window.current.style.width = "100%";
window.current.style.height = "100%";
if (props.onResize) props.onResize();
maximize() {
this.window.current.style.width = "100%";
this.window.current.style.height = "100%";
if (this.props.onResize) this.props.onResize();
const width = this.window.current.offsetWidth;
const height = this.window.current.offsetHeight;
const left = parseInt(this.window.current.style.left);
const top = parseInt(this.window.current.style.top);
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 > this.maxY) this.window.current.style.top = (this.maxY - height) + "px";
if (right > this.maxX) this.window.current.style.left = (this.maxX - width) + "px";
if (bottom > maxY) window.current.style.top = (maxY - height) + "px";
if (right > maxX) window.current.style.left = (maxX - width) + "px";
const newLeft = parseInt(this.window.current.style.left);
const newTop = parseInt(this.window.current.style.top);
const newLeft = parseInt(window.current.style.left);
const newTop = parseInt(window.current.style.top);
// For small screens it's possible this pushes us off the other direction... we need to readjust size
if (newTop < this.minY) {
const difference = this.minY - newTop;
this.window.current.style.top = this.minY + "px";
this.window.current.style.height = (height - difference) + "px";
// 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 < this.minX) {
const difference = this.minX - newLeft;
this.window.current.style.left = this.minX + "px";
this.window.current.style.height = (width - 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]);
async close() {
const close = useCallback(async () => {
let shouldClose = true;
const confirmClose = typeof(this.props.confirmClose) == "function" ? this.props.confirmClose() : this.props.confirmClose;
if (confirmClose) {
this.setState({modalOpen: true});
shouldClose = await this.confirmClose();
this.setState({modalOpen: false});
const didConfirmClose = typeof(props.confirmClose) == "function" ? props.confirmClose() : props.confirmClose;
if (didConfirmClose) {
setOpen(true);
shouldClose = await confirmClose(props.confirmationText);
setOpen(false);
}
if (this.props.close && shouldClose) this.props.close();
}
if (props.close && shouldClose) props.close();
}, []);
confirmClose() {
return new Promise(resolve => {
Modals.showConfirmationModal(Strings.Modals.confirmAction, this.props.confirmationText, {
danger: true,
confirmText: Strings.Modals.close,
onConfirm: () => {resolve(true);},
onCancel: () => {resolve(false);}
});
});
}
const className = `floating-window${` ${props.className}` || ""}${props.resizable ? " resizable" : ""}${modalOpen ? " modal-open" : ""}`;
const styles = {height: props.height, width: props.width, left: position.x || 0, top: position.y || 0};
return <div id={props.id} className={className} ref={window} style={styles}>
<div className="floating-window-titlebar" ref={titlebar}>
<span className="title">{props.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">
{props.children}
</div>
</div>;
}

View File

@ -1,25 +1,25 @@
import {WebpackModules, React, ReactDOM, DOMManager} from "modules";
import {WebpackModules, React, ReactDOM, DOMManager, Events} from "modules";
import FloatingWindowContainer from "./floating/container";
/* eslint-disable new-cap */
const AppLayerProvider = WebpackModules.getByDisplayName("AppLayerProvider");
let hasInitialized = false;
export default class FloatingWindows {
static initialize() {
const containerRef = React.createRef();
const container = <FloatingWindowContainer ref={containerRef} />;
const container = <FloatingWindowContainer />;
const wrapped = AppLayerProvider
? React.createElement(AppLayerProvider().props.layerContext.Provider, {value: [document.querySelector("#app-mount > .layerContainer-2v_Sit")]}, container) // eslint-disable-line new-cap
: container;
const div = DOMManager.parseHTML(`<div id="floating-windows-layer">`);
DOMManager.bdBody.append(div);
ReactDOM.render(wrapped, div);
this.ref = containerRef;
hasInitialized = true;
}
static open(window) {
if (!this.ref) this.initialize();
return this.ref.current.open(window);
if (!hasInitialized) this.initialize();
return Events.emit("open-window", window);
}
}