From 0bd7e8502b4b4dffe239962f0cb428aeceb703d9 Mon Sep 17 00:00:00 2001 From: Prozilla Date: Sat, 4 May 2024 14:50:17 +0200 Subject: [PATCH] Added app error handling + made modals global --- package-lock.json | 37 ++++++------------ package.json | 1 + src/App.jsx | 19 +++++---- .../apps/file-explorer/FileExplorer.jsx | 8 ++-- .../apps/settings/tabs/AppOption.jsx | 4 +- .../apps/settings/tabs/AppearanceSettings.jsx | 2 +- .../apps/text-editor/TextEditor.jsx | 2 +- .../text-editor/overrides/MarkdownLink.jsx | 8 ++-- src/components/desktop/Desktop.jsx | 6 +-- src/components/modals/ModalsView.jsx | 37 +++++++++--------- .../modals/file-selector/FileSelector.jsx | 6 +-- src/components/taskbar/Taskbar.jsx | 7 +--- src/components/windows/WindowFallbackView.jsx | 28 +++++++++++++ src/components/windows/WindowView.jsx | 39 +++++++++++-------- src/features/modals/modalsManager.js | 6 +-- src/hooks/modals/alert.js | 12 +++--- src/hooks/modals/contextMenu.js | 7 ++-- src/hooks/modals/modals.js | 19 --------- src/hooks/modals/modalsContext.js | 33 ++++++++++++++++ src/hooks/modals/modalsManagerContext.js | 29 ++++++++++++++ src/hooks/modals/windowedModal.js | 12 +++--- 21 files changed, 192 insertions(+), 130 deletions(-) create mode 100644 src/components/windows/WindowFallbackView.jsx delete mode 100644 src/hooks/modals/modals.js create mode 100644 src/hooks/modals/modalsContext.js create mode 100644 src/hooks/modals/modalsManagerContext.js diff --git a/package-lock.json b/package-lock.json index 44d9925..8349a7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index b46a460..ea15dce 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/App.jsx b/src/App.jsx index f754336..881f4d4 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -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() { - -
- - - - -
-
+ + +
+ + + + +
+
+
diff --git a/src/components/apps/file-explorer/FileExplorer.jsx b/src/components/apps/file-explorer/FileExplorer.jsx index 348daf8..25e6b70 100644 --- a/src/components/apps/file-explorer/FileExplorer.jsx +++ b/src/components/apps/file-explorer/FileExplorer.jsx @@ -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) => { if (isSelector) { @@ -65,7 +65,7 @@ export function FileExplorer({ startPath, selectorMode, Footer, modalsManager, o }}/> }); - const { onContextMenu: onContextMenuFolder } = useContextMenu({ modalsManager, Actions: (props) => + const { onContextMenu: onContextMenuFolder } = useContextMenu({ Actions: (props) => { changeDirectory(folder.linkedPath ?? folder.name); diff --git a/src/components/apps/settings/tabs/AppOption.jsx b/src/components/apps/settings/tabs/AppOption.jsx index 2cef643..5bb9cce 100644 --- a/src/components/apps/settings/tabs/AppOption.jsx +++ b/src/components/apps/settings/tabs/AppOption.jsx @@ -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) => windowsManager.open(app.id)}/> { diff --git a/src/components/apps/settings/tabs/AppearanceSettings.jsx b/src/components/apps/settings/tabs/AppearanceSettings.jsx index 569adc8..c8e89e2 100644 --- a/src/components/apps/settings/tabs/AppearanceSettings.jsx +++ b/src/components/apps/settings/tabs/AppearanceSettings.jsx @@ -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); diff --git a/src/components/apps/text-editor/TextEditor.jsx b/src/components/apps/text-editor/TextEditor.jsx index 4ae8776..7d546c5 100644 --- a/src/components/apps/text-editor/TextEditor.jsx +++ b/src/components/apps/text-editor/TextEditor.jsx @@ -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 () => { diff --git a/src/components/apps/text-editor/overrides/MarkdownLink.jsx b/src/components/apps/text-editor/overrides/MarkdownLink.jsx index 8a8503f..ad7e047 100644 --- a/src/components/apps/text-editor/overrides/MarkdownLink.jsx +++ b/src/components/apps/text-editor/overrides/MarkdownLink.jsx @@ -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) => diff --git a/src/components/desktop/Desktop.jsx b/src/components/desktop/Desktop.jsx index 481006f..7cf2bc8 100644 --- a/src/components/desktop/Desktop.jsx +++ b/src/components/desktop/Desktop.jsx @@ -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} > - {showIcons && { +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 ( -
- {modals?.map((modal) => - - )} -
- ); + return
+ {sortedModals?.map((modal) => + + )} +
; }); \ No newline at end of file diff --git a/src/components/modals/file-selector/FileSelector.jsx b/src/components/modals/file-selector/FileSelector.jsx index 1d06160..428ad58 100644 --- a/src/components/modals/file-selector/FileSelector.jsx +++ b/src/components/modals/file-selector/FileSelector.jsx @@ -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}> - { 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 (<> -
{ + 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; +} \ No newline at end of file diff --git a/src/components/windows/WindowView.jsx b/src/components/windows/WindowView.jsx index bade872..79f736f 100644 --- a/src/components/windows/WindowView.jsx +++ b/src/components/windows/WindowView.jsx @@ -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) => { @@ -142,7 +141,6 @@ export const WindowView = memo(({ id, app, size, position, onInteract, options, return (
-
- + } + onReset={(details) => { + // Reset the state of your app so the error doesn't happen again + }} + onError={(error) => { + console.error(error); + }} + > + +
diff --git a/src/features/modals/modalsManager.js b/src/features/modals/modalsManager.js index 3eb3109..0137455 100644 --- a/src/features/modals/modalsManager.js +++ b/src/features/modals/modalsManager.js @@ -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); }); diff --git a/src/hooks/modals/alert.js b/src/hooks/modals/alert.js index fe5a4f4..4a11333 100644 --- a/src/hooks/modals/alert.js +++ b/src/hooks/modals/alert.js @@ -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) =>

{text}

diff --git a/src/hooks/modals/contextMenu.js b/src/hooks/modals/contextMenu.js index 8fba7d8..aa8f11d 100644 --- a/src/hooks/modals/contextMenu.js +++ b/src/hooks/modals/contextMenu.js @@ -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(); diff --git a/src/hooks/modals/modals.js b/src/hooks/modals/modals.js deleted file mode 100644 index 4633b0e..0000000 --- a/src/hooks/modals/modals.js +++ /dev/null @@ -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]; -} \ No newline at end of file diff --git a/src/hooks/modals/modalsContext.js b/src/hooks/modals/modalsContext.js new file mode 100644 index 0000000..9d4d596 --- /dev/null +++ b/src/hooks/modals/modalsContext.js @@ -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} + */ +export function ModalsProvider({ children, modalsManager }) { + const [modals, setModals] = useState([]); + + const updateModals = useCallback((updatedModals) => { + setModals(Object.values(updatedModals)); + }, []); + + modalsManager.setUpdateModals(updateModals); + + return ( + + {children} + + ); +} + +/** + * @returns {Modal[]} + */ +export function useModals() { + return useContext(ModalsContext); +} \ No newline at end of file diff --git a/src/hooks/modals/modalsManagerContext.js b/src/hooks/modals/modalsManagerContext.js new file mode 100644 index 0000000..a6c3bc4 --- /dev/null +++ b/src/hooks/modals/modalsManagerContext.js @@ -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} + */ +export function ModalsManagerProvider({ children }) { + const modalsManager = new ModalsManager(); + + return ( + + + {children} + + + ); +} + +/** + * @returns {ModalsManager} + */ +export function useModalsManager() { + return useContext(modalsManagerContext); +} \ No newline at end of file diff --git a/src/hooks/modals/windowedModal.js b/src/hooks/modals/windowedModal.js index bd3646c..cbf2346 100644 --- a/src/hooks/modals/windowedModal.js +++ b/src/hooks/modals/windowedModal.js @@ -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]);