Added app error handling + made modals global
This commit is contained in:
parent
bca11ddfd2
commit
0bd7e8502b
21 changed files with 192 additions and 130 deletions
37
package-lock.json
generated
37
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
19
src/App.jsx
19
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() {
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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={() => {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
});
|
||||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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"]}
|
||||
|
|
|
|||
28
src/components/windows/WindowFallbackView.jsx
Normal file
28
src/components/windows/WindowFallbackView.jsx
Normal 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;
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
}
|
||||
33
src/hooks/modals/modalsContext.js
Normal file
33
src/hooks/modals/modalsContext.js
Normal 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);
|
||||
}
|
||||
29
src/hooks/modals/modalsManagerContext.js
Normal file
29
src/hooks/modals/modalsManagerContext.js
Normal 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);
|
||||
}
|
||||
|
|
@ -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]);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue