Added app error handling + made modals global

This commit is contained in:
Prozilla 2024-05-04 14:50:17 +02:00
parent bca11ddfd2
commit 0bd7e8502b
No known key found for this signature in database
GPG key ID: 5858DFE71CAF31EE
21 changed files with 192 additions and 130 deletions

37
package-lock.json generated
View file

@ -22,6 +22,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-draggable": "^4.4.5",
"react-error-boundary": "^4.0.13",
"react-ga4": "^2.1.0",
"react-scripts": "5.0.1",
"react-svg": "^16.1.18",
@ -31,7 +32,6 @@
},
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"babel-plugin-wildcard": "^7.0.0",
"eslint-plugin-jsdoc": "^46.4.6",
"gh-pages": "^5.0.0"
}
@ -5637,30 +5637,6 @@
"resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz",
"integrity": "sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA=="
},
"node_modules/babel-plugin-wildcard": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/babel-plugin-wildcard/-/babel-plugin-wildcard-7.0.0.tgz",
"integrity": "sha512-3duaRaULzxmmgr+YO9EPEE3m/irVyOWKvXCOz2S41tlp0g2UarSEtpM++D3IS+asK205OOqW1ajnjFeoRRqfZg==",
"dev": true,
"dependencies": {
"rimraf": "^2.6.2"
},
"bin": {
"bpwc": "index.js"
}
},
"node_modules/babel-plugin-wildcard/node_modules/rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"dev": true,
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
}
},
"node_modules/babel-preset-current-node-syntax": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz",
@ -15079,6 +15055,17 @@
"react-dom": ">= 16.3.0"
}
},
"node_modules/react-error-boundary": {
"version": "4.0.13",
"resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.0.13.tgz",
"integrity": "sha512-b6PwbdSv8XeOSYvjt8LpgpKrZ0yGdtZokYwkwV2wlcZbxgopHX/hgPl5VgpnoVOWd868n1hktM8Qm4b+02MiLQ==",
"dependencies": {
"@babel/runtime": "^7.12.5"
},
"peerDependencies": {
"react": ">=16.13.1"
}
},
"node_modules/react-error-overlay": {
"version": "6.0.11",
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz",

View file

@ -28,6 +28,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-draggable": "^4.4.5",
"react-error-boundary": "^4.0.13",
"react-ga4": "^2.1.0",
"react-scripts": "5.0.1",
"react-svg": "^16.1.18",

View file

@ -9,6 +9,7 @@ import { ModalsView } from "./components/modals/ModalsView.jsx";
import { useEffect } from "react";
import { ZIndexManagerProvider } from "./hooks/z-index/zIndexManagerContext.js";
import { TrackingManager } from "./features/tracking/trackingManager.js";
import { ModalsManagerProvider } from "./hooks/modals/modalsManagerContext.js";
TrackingManager.initialize();
@ -29,14 +30,16 @@ function App() {
<VirtualRootProvider>
<ZIndexManagerProvider>
<WindowsManagerProvider>
<SettingsManagerProvider>
<div className={styles.App}>
<Taskbar/>
<WindowsView/>
<ModalsView/>
<Desktop/>
</div>
</SettingsManagerProvider>
<ModalsManagerProvider>
<SettingsManagerProvider>
<div className={styles.App}>
<Taskbar/>
<WindowsView/>
<ModalsView/>
<Desktop/>
</div>
</SettingsManagerProvider>
</ModalsManagerProvider>
</WindowsManagerProvider>
</ZIndexManagerProvider>
</VirtualRootProvider>

View file

@ -31,7 +31,7 @@ import { SELECTOR_MODE } from "../../../config/apps/fileExplorer.config.js";
* @param {import("./directory-list/DirectoryList.jsx").onSelectionChange} props.onSelectionChange
* @param {Function} props.onSelectionFinish
*/
export function FileExplorer({ startPath, selectorMode, Footer, modalsManager, onSelectionChange, onSelectionFinish }) {
export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChange, onSelectionFinish }) {
const isSelector = (Footer != null && selectorMode != null && selectorMode !== SELECTOR_MODE.NONE);
const virtualRoot = useVirtualRoot();
@ -41,8 +41,8 @@ export function FileExplorer({ startPath, selectorMode, Footer, modalsManager, o
const [showHidden] = useState(true);
const { history, stateIndex, pushState, undo, redo, undoAvailable, redoAvailable } = useHistory(currentDirectory.path);
const { openWindowedModal } = useWindowedModal({ modalsManager });
const { onContextMenu: onContextMenuFile } = useContextMenu({ modalsManager, Actions: (props) =>
const { openWindowedModal } = useWindowedModal();
const { onContextMenu: onContextMenuFile } = useContextMenu({ Actions: (props) =>
<Actions {...props}>
<ClickAction label={!isSelector ? "Open" : "Select"} onTrigger={(event, file) => {
if (isSelector) {
@ -65,7 +65,7 @@ export function FileExplorer({ startPath, selectorMode, Footer, modalsManager, o
}}/>
</Actions>
});
const { onContextMenu: onContextMenuFolder } = useContextMenu({ modalsManager, Actions: (props) =>
const { onContextMenu: onContextMenuFolder } = useContextMenu({ Actions: (props) =>
<Actions {...props}>
<ClickAction label="Open" onTrigger={(event, folder) => {
changeDirectory(folder.linkedPath ?? folder.name);

View file

@ -11,13 +11,13 @@ import { removeFromArray } from "../../../../features/_utils/array.utils.js";
import { useSettingsManager } from "../../../../hooks/settings/settingsManagerContext.js";
import { SettingsManager } from "../../../../features/settings/settingsManager.js";
export function AppOption({ app, pins, setPins, modalsManager }) {
export function AppOption({ app, pins, setPins }) {
const isPinned = pins.includes(app.id);
const settingsManager = useSettingsManager();
const windowsManager = useWindowsManager();
const { onContextMenu } = useContextMenu({ modalsManager, Actions: (props) =>
const { onContextMenu } = useContextMenu({ Actions: (props) =>
<Actions {...props}>
<ClickAction label="Launch" icon={AppsManager.getAppIconUrl(app.id)} onTrigger={() => windowsManager.open(app.id)}/>
<ClickAction label={isPinned ? "Unpin from taskbar" : "Pin to taskbar"} icon={faThumbTack} onTrigger={() => {

View file

@ -22,7 +22,7 @@ export function AppearanceSettings({ modalsManager }) {
const settingsManager = useSettingsManager();
const [wallpaper, setWallpaper] = useState(null);
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop);
const { openWindowedModal } = useWindowedModal({ modalsManager });
const { openWindowedModal } = useWindowedModal();
useEffect(() => {
settings.get("wallpaper", setWallpaper);

View file

@ -30,7 +30,7 @@ export function TextEditor({ file, setTitle, setIconUrl, close, mode, app, modal
const [content, setContent] = useState(file?.content ?? "");
const [unsavedChanges, setUnsavedChanges] = useState(file == null);
const [zoom, setZoom] = useState(DEFAULT_ZOOM);
const { openWindowedModal } = useWindowedModal({ modalsManager });
const { openWindowedModal } = useWindowedModal();
useEffect(() => {
(async () => {

View file

@ -3,7 +3,6 @@ import { faExternalLink } from "@fortawesome/free-solid-svg-icons";
import { useContextMenu } from "../../../../hooks/modals/contextMenu.js";
import { Actions } from "../../../actions/Actions.jsx";
import { ClickAction } from "../../../actions/actions/ClickAction.jsx";
import ModalsManager from "../../../../features/modals/modalsManager.js";
import { VirtualFile } from "../../../../features/virtual-drive/file/virtualFile.js";
import { useWindowedModal } from "../../../../hooks/modals/windowedModal.js";
import AppsManager from "../../../../features/apps/appsManager.js";
@ -18,14 +17,13 @@ import { APPS } from "../../../../config/apps.config.js";
* @param {object} props
* @param {string} props.href
* @param {*} props.children
* @param {ModalsManager} props.modalsManager
* @param {WindowsManager} props.windowsManager
* @param {Function} props.setCurrentFile
* @param {VirtualFile} props.currentFile
* @param {App} props.app
*/
export function MarkdownLink({ href, children, modalsManager, windowsManager, currentFile, setCurrentFile, app, ...props }) {
const { openWindowedModal } = useWindowedModal({ modalsManager });
export function MarkdownLink({ href, children, windowsManager, currentFile, setCurrentFile, app, ...props }) {
const { openWindowedModal } = useWindowedModal();
const onClick = (event) => {
event.preventDefault();
@ -55,7 +53,7 @@ export function MarkdownLink({ href, children, modalsManager, windowsManager, cu
}
};
const { onContextMenu } = useContextMenu({ modalsManager, Actions: (props) =>
const { onContextMenu } = useContextMenu({ Actions: (props) =>
<Actions {...props}>
<ClickAction label="Open link" icon={faExternalLink} onTrigger={onClick}/>
</Actions>

View file

@ -3,8 +3,6 @@ import { SettingsManager } from "../../features/settings/settingsManager.js";
import { useSettingsManager } from "../../hooks/settings/settingsManagerContext.js";
import styles from "./Desktop.module.css";
import { useEffect } from "react";
import { useModals } from "../../hooks/modals/modals.js";
import { ModalsView } from "../modals/ModalsView.jsx";
import { useWindowsManager } from "../../hooks/windows/windowsManagerContext.js";
import { useContextMenu } from "../../hooks/modals/contextMenu.js";
import { FALLBACK_ICON_SIZE, FALLBACK_WALLPAPER } from "../../config/desktop.config.js";
@ -24,11 +22,12 @@ import { isValidInteger } from "../../features/_utils/number.utils.js";
import { useWindowedModal } from "../../hooks/modals/windowedModal.js";
import { Share } from "../modals/share/Share.jsx";
import ModalsManager from "../../features/modals/modalsManager.js";
import { useModalsManager } from "../../hooks/modals/modalsManagerContext.js";
export const Desktop = memo(() => {
const settingsManager = useSettingsManager();
const [wallpaper, setWallpaper] = useState(null);
const [modalsManager, modals] = useModals();
const modalsManager = useModalsManager();
const windowsManager = useWindowsManager();
const virtualRoot = useVirtualRoot();
const [showIcons, setShowIcons] = useState(false);
@ -138,7 +137,6 @@ export const Desktop = memo(() => {
className={styles.Container}
onContextMenu={onContextMenu}
>
<ModalsView modalsManager={modalsManager} modals={modals}/>
{showIcons && <DirectoryList
directory={directory}
className={styles.Content}

View file

@ -1,29 +1,30 @@
import { memo, useEffect, useRef } from "react";
import { Modal } from "../../features/modals/modal.js";
import ModalsManager from "../../features/modals/modalsManager.js";
import { memo, useEffect, useRef, useState } from "react";
import { ModalView } from "./ModalView.jsx";
import styles from "./ModalsView.module.css";
import { useModals } from "../../hooks/modals/modalsContext.js";
import { useModalsManager } from "../../hooks/modals/modalsManagerContext.js";
/**
* @param {object} root
* @param {ModalsManager} root.modalsManager
* @param {Modal[]} root.modals
* @param {import("react").CSSProperties} root.style
* @param {import("react").className} root.className
*/
export const ModalsView = memo(({ modalsManager, modals, style, className, ...props }) => {
export const ModalsView = memo(() => {
const ref = useRef(null);
const modals = useModals();
const modalsManager = useModalsManager();
const [sortedModals, setSortedModals] = useState([]);
// Sort modals
useEffect(() => {
setSortedModals([...modals].sort((modalA, modalB) =>
modalA.lastInteraction - modalB.lastInteraction
));
}, [modals]);
useEffect(() => {
if (modalsManager)
modalsManager.containerRef = ref;
}, [modalsManager, ref]);
return (
<div ref={ref} style={style} className={`${styles.Container} ${className ?? ""}`} {...props}>
{modals?.map((modal) =>
<ModalView key={modal.id} modal={modal}/>
)}
</div>
);
return <div ref={ref} className={styles.Container}>
{sortedModals?.map((modal) =>
<ModalView key={modal.id} modal={modal}/>
)}
</div>;
});

View file

@ -1,14 +1,13 @@
import { useState } from "react";
import { APP_ICONS } from "../../../config/apps.config.js";
import { SELECTOR_MODE } from "../../../config/apps/fileExplorer.config.js";
import { useModals } from "../../../hooks/modals/modals.js";
import { Button } from "../../_utils/button/Button.jsx";
import { FileExplorer } from "../../apps/file-explorer/FileExplorer.jsx";
import { ModalsView } from "../ModalsView.jsx";
import { WindowedModal } from "../_utils/WindowedModal.jsx";
import styles from "./FileSelector.module.css";
import { VirtualFile } from "../../../features/virtual-drive/file/virtualFile.js";
import Modal from "../../../features/modals/modal.js";
import { useModalsManager } from "../../../hooks/modals/modalsManagerContext.js";
/**
* @callback onFinish
@ -26,7 +25,7 @@ import Modal from "../../../features/modals/modal.js";
export function FileSelector({ modal, params, type, allowedFormats, onFinish, ...props }) {
const multi = (type === SELECTOR_MODE.MULTIPLE);
const [modalsManager, modals] = useModals();
const modalsManager = useModalsManager();
const [selection, setSelection] = useState(multi ? [] : null);
const [directory, setDirectory] = useState(null);
@ -58,7 +57,6 @@ export function FileSelector({ modal, params, type, allowedFormats, onFinish, ..
iconUrl: APP_ICONS.FILE_EXPLORER,
...params,
}} {...props}>
<ModalsView modalsManager={modalsManager} modals={modals}/>
<FileExplorer
modalsManager={modalsManager}
selectorMode={type}

View file

@ -15,17 +15,16 @@ import { useScrollWithShadow } from "../../hooks/_utils/scrollWithShadows.js";
import { AppButton } from "./app-icon/AppIcon.jsx";
import { useContextMenu } from "../../hooks/modals/contextMenu.js";
import { Actions } from "../actions/Actions.jsx";
import { useModals } from "../../hooks/modals/modals.js";
import { ClickAction } from "../actions/actions/ClickAction.jsx";
import { APPS, APP_NAMES } from "../../config/apps.config.js";
import { useWindowsManager } from "../../hooks/windows/windowsManagerContext.js";
import { ModalsView } from "../modals/ModalsView.jsx";
import { TASKBAR_HEIGHT } from "../../config/taskbar.config.js";
import { useSettingsManager } from "../../hooks/settings/settingsManagerContext.js";
import { SettingsManager } from "../../features/settings/settingsManager.js";
import { useWindows } from "../../hooks/windows/windowsContext.js";
import { ZIndexManager } from "../../features/z-index/zIndexManager.js";
import { useZIndex } from "../../hooks/z-index/zIndex.js";
import { useModalsManager } from "../../hooks/modals/modalsManagerContext.js";
export const Taskbar = memo(() => {
const ref = useRef(null);
@ -41,7 +40,7 @@ export const Taskbar = memo(() => {
color: { a: 25 }
} });
const inputRef = useRef(null);
const [modalsManager, modals] = useModals();
const modalsManager = useModalsManager();
const windowsManager = useWindowsManager();
const windows = useWindows();
const { onContextMenu } = useContextMenu({ modalsManager, Actions: (props) =>
@ -53,7 +52,6 @@ export const Taskbar = memo(() => {
});
const [pins, setPins] = useState([]);
const zIndex = useZIndex({ groupIndex: ZIndexManager.GROUPS.TASKBAR, index: 0 });
const modalsZIndex = useZIndex({ groupIndex: ZIndexManager.GROUPS.TASKBAR, index: 1 });
const apps = useMemo(() => AppsManager.APPS.sort((appA, appB) => {
const indexA = pins.indexOf(appA.id);
@ -132,7 +130,6 @@ export const Taskbar = memo(() => {
};
return (<>
<ModalsView modalsManager={modalsManager} modals={modals} style={{ zIndex: modalsZIndex }}/>
<div
style={{ "--taskbar-height": `${TASKBAR_HEIGHT}px`, zIndex }}
className={styles["Taskbar"]}

View file

@ -0,0 +1,28 @@
import { useEffect, useState } from "react";
import { useModalsManager } from "../../hooks/modals/modalsManagerContext.js";
import { useAlert } from "../../hooks/modals/alert.js";
import AppsManager from "../../features/apps/appsManager.js";
import Vector2 from "../../features/math/vector2.js";
export default function WindowFallbackView({ error, resetErrorBoundary, app, closeWindow }) {
const modalsManager = useModalsManager();
const { alert } = useAlert({ modalsManager });
const [alerted, setAlerted] = useState(false);
useEffect(() => {
if (alerted)
return;
setAlerted(true);
closeWindow();
alert({
title: `${app.name} has stopped working`,
text: `${error.name}: ${error.message}`,
iconUrl: AppsManager.getAppIconUrl(app.id),
size: new Vector2(350, 150),
single: true
});
}, [alerted, alert, app.id, app.name, error.message, closeWindow, error.name]);
return;
}

View file

@ -9,8 +9,6 @@ import App from "../../features/apps/app.js";
import Vector2 from "../../features/math/vector2.js";
import { faWindowMaximize } from "@fortawesome/free-regular-svg-icons";
import utilStyles from "../../styles/utils.module.css";
import { useModals } from "../../hooks/modals/modals.js";
import { ModalsView } from "../modals/ModalsView.jsx";
import { useContextMenu } from "../../hooks/modals/contextMenu.js";
import AppsManager from "../../features/apps/appsManager.js";
import { ClickAction } from "../actions/actions/ClickAction.jsx";
@ -24,6 +22,8 @@ import { useWindowedModal } from "../../hooks/modals/windowedModal.js";
import { Divider } from "../actions/actions/Divider.jsx";
import ModalsManager from "../../features/modals/modalsManager.js";
import { Share } from "../modals/share/Share.jsx";
import { ErrorBoundary } from "react-error-boundary";
import WindowFallbackView from "./WindowFallbackView.jsx";
/**
* @typedef {object} windowProps
@ -52,8 +52,7 @@ import { Share } from "../modals/share/Share.jsx";
export const WindowView = memo(({ id, app, size, position, onInteract, options, active, fullscreen, minimized, toggleMinimized, index }) => {
const windowsManager = useWindowsManager();
const nodeRef = useRef(null);
const [modalsManager, modals] = useModals();
const { openWindowedModal } = useWindowedModal({ modalsManager });
const { openWindowedModal } = useWindowedModal();
const [startSize, setStartSize] = useState(size);
const [startPosition, setStartPosition] = useState(position);
@ -63,7 +62,7 @@ export const WindowView = memo(({ id, app, size, position, onInteract, options,
const [iconUrl, setIconUrl] = useState(AppsManager.getAppIconUrl(app.id));
const zIndex = useZIndex({ groupIndex: ZIndexManager.GROUPS.WINDOWS, index });
const { onContextMenu, ShortcutsListener } = useContextMenu({ modalsManager, Actions: (props) =>
const { onContextMenu, ShortcutsListener } = useContextMenu({ Actions: (props) =>
<Actions {...props}>
<ClickAction label="Minimize" icon={faMinus} onTrigger={toggleMinimized}/>
<ClickAction label="Maximize" icon={faExpand} shortcut={["F11"]} onTrigger={() => {
@ -142,7 +141,6 @@ export const WindowView = memo(({ id, app, size, position, onInteract, options,
return (<div style={{ zIndex, position: !maximized ? "relative" : null }}>
<ShortcutsListener/>
<ModalsView modalsManager={modalsManager} modals={modals} style={{ zIndex: 1 }}/>
<Draggable
key={id}
axis="both"
@ -203,16 +201,25 @@ export const WindowView = memo(({ id, app, size, position, onInteract, options,
</button>
</div>
<div className={styles["Window-content"]}>
<app.WindowContent
{...options}
app={app}
setTitle={setTitle}
setIconUrl={setIconUrl}
close={close}
focus={focus}
active={active}
modalsManager={modalsManager}
/>
<ErrorBoundary
FallbackComponent={(props) => <WindowFallbackView app={app} closeWindow={close} {...props}/>}
onReset={(details) => {
// Reset the state of your app so the error doesn't happen again
}}
onError={(error) => {
console.error(error);
}}
>
<app.WindowContent
{...options}
app={app}
setTitle={setTitle}
setIconUrl={setIconUrl}
close={close}
focus={focus}
active={active}
/>
</ErrorBoundary>
</div>
</div>
</div>

View file

@ -13,10 +13,10 @@ export default class ModalsManager {
/**
* @param {Modal} modal
* @param {boolean} closeOthers - Set to false to preserve other open modals
* @param {boolean} single - Set to false to preserve other open modals
*/
open(modal, closeOthers = true) {
if (closeOthers) {
open(modal, single = true) {
if (single) {
this.modalIds.forEach((id) => {
this.close(id, false);
});

View file

@ -3,7 +3,6 @@ import { DialogBox } from "../../components/modals/dialog-box/DialogBox.jsx";
import { DIALOG_CONTENT_TYPES } from "../../config/modals.config.js";
import Vector2 from "../../features/math/vector2.js";
import { useWindowedModal } from "./windowedModal.js";
import ModalsManager from "../../features/modals/modalsManager.js";
/**
* @typedef alertType
@ -14,18 +13,17 @@ import ModalsManager from "../../features/modals/modalsManager.js";
*/
/**
* @param {object} props
* @param {ModalsManager} props.modalsManager
* @returns {{ alert: alertType }}
*/
export function useAlert({ modalsManager }) {
const { openWindowedModal } = useWindowedModal({ modalsManager });
export function useAlert() {
const { openWindowedModal } = useWindowedModal();
const alert = useCallback(({ title, text, iconUrl }) => {
const alert = useCallback(({ title, text, iconUrl, size, single }) => {
openWindowedModal({
title: title ?? "Alert",
iconUrl,
size: new Vector2(300, 150),
size: size ?? new Vector2(300, 150),
single,
Modal: (props) =>
<DialogBox {...props}>
<p>{text}</p>

View file

@ -1,8 +1,8 @@
import { useCallback } from "react";
import Vector2 from "../../features/math/vector2.js";
import Modal from "../../features/modals/modal.js";
import ModalsManager from "../../features/modals/modalsManager.js";
import { STYLES } from "../../components/actions/Actions.jsx";
import { useModalsManager } from "./modalsManagerContext.js";
/**
* @callback onContextMenuType
@ -13,14 +13,15 @@ import { STYLES } from "../../components/actions/Actions.jsx";
/**
* @param {object} props
* @param {ModalsManager} props.modalsManager
* @param {import("../../components/actions/Actions.jsx").actionsType} props.Actions
* @returns {{
* onContextMenu: onContextMenuType,
* ShortcutsListener: import("../../components/actions/Actions.jsx").actionsType
* }}
*/
export function useContextMenu({ modalsManager, Actions }) {
export function useContextMenu({ Actions }) {
const modalsManager = useModalsManager();
// Open a new modal when context menu is triggered
const onContextMenu = useCallback((event, params = {}) => {
event.preventDefault();

View file

@ -1,19 +0,0 @@
import { useCallback, useState } from "react";
import ModalsManager from "../../features/modals/modalsManager.js";
import Modal from "../../features/modals/modal.js";
/**
* @returns {[ModalsManager, Modal[]]}
*/
export function useModals() {
const modalsManager = new ModalsManager();
const [modals, setModals] = useState([]);
const updateModals = useCallback((updatedModals) => {
setModals(Object.values(updatedModals));
}, []);
modalsManager.setUpdateModals(updateModals);
return [modalsManager, modals];
}

View file

@ -0,0 +1,33 @@
import { createContext, useCallback, useContext, useState } from "react";
import Modal from "../../features/modals/modal.js";
const ModalsContext = createContext();
/**
* @param props
* @param props.children
* @param props.modalsManager
* @returns {import("react").Provider<any>}
*/
export function ModalsProvider({ children, modalsManager }) {
const [modals, setModals] = useState([]);
const updateModals = useCallback((updatedModals) => {
setModals(Object.values(updatedModals));
}, []);
modalsManager.setUpdateModals(updateModals);
return (
<ModalsContext.Provider value={modals}>
{children}
</ModalsContext.Provider>
);
}
/**
* @returns {Modal[]}
*/
export function useModals() {
return useContext(ModalsContext);
}

View file

@ -0,0 +1,29 @@
import { createContext, useContext } from "react";
import ModalsManager from "../../features/modals/modalsManager.js";
import { ModalsProvider } from "./modalsContext.js";
const modalsManagerContext = createContext();
/**
* @param {object} props
* @param {import("react").ElementType} props.children
* @returns {import("react").Provider<any>}
*/
export function ModalsManagerProvider({ children }) {
const modalsManager = new ModalsManager();
return (
<modalsManagerContext.Provider value={modalsManager}>
<ModalsProvider modalsManager={modalsManager}>
{children}
</ModalsProvider>
</modalsManagerContext.Provider>
);
}
/**
* @returns {ModalsManager}
*/
export function useModalsManager() {
return useContext(modalsManagerContext);
}

View file

@ -2,7 +2,7 @@ import { useCallback } from "react";
import Modal from "../../features/modals/modal.js";
import Vector2 from "../../features/math/vector2.js";
import { DEFAULT_DIALOG_SIZE } from "../../config/modals.config.js";
import ModalsManager from "../../features/modals/modalsManager.js";
import { useModalsManager } from "./modalsManagerContext.js";
/**
* @callback windowedModalType
@ -20,11 +20,11 @@ import ModalsManager from "../../features/modals/modalsManager.js";
*/
/**
* @param {object} props
* @param {ModalsManager} props.modalsManager
* @returns {{ openWindowedModal: openWindowedModalType }}
*/
export function useWindowedModal({ modalsManager }) {
export function useWindowedModal() {
const modalsManager = useModalsManager();
const openWindowedModal = useCallback(({ Modal: WindowedModal, ...params }) => {
const size = params.size ?? DEFAULT_DIALOG_SIZE;
let positionX = (window.innerWidth - size.x) / 4;
@ -42,7 +42,9 @@ export function useWindowedModal({ modalsManager }) {
.setDismissible(false)
.setProps({ params });
modalsManager.open(newModal, false);
const single = params.single ?? false;
modalsManager.open(newModal, single);
return newModal;
}, [modalsManager]);