From af29c8bc5ea38c66c882812ccac8a5a9f4e92c15 Mon Sep 17 00:00:00 2001 From: Prozilla Date: Fri, 11 Aug 2023 15:27:27 +0200 Subject: [PATCH 1/6] Added modals --- src/App.jsx | 15 +++ src/App.module.css | 5 + .../applications/calculator/Calculator.jsx | 2 +- src/components/desktop/Desktop.jsx | 19 +++- src/components/modals/ModalView.jsx | 17 ++++ src/components/modals/ModalView.module.css | 14 +++ src/components/modals/ModalsView.jsx | 18 ++++ .../modals/context-menu/ContextMenu.jsx | 5 + .../context-menu/ContextMenu.module.css | 5 + .../windows/{Window.jsx => WindowView.jsx} | 4 +- ...indow.module.css => WindowView.module.css} | 6 +- src/components/windows/WindowsView.jsx | 4 +- src/features/applications/applications.js | 2 +- src/features/modals/modal.js | 99 +++++++++++++++++++ src/features/modals/modals.js | 86 ++++++++++++++++ src/hooks/modals/ModalsContext.js | 19 ++++ src/index.js | 2 +- src/styles/global.css | 8 ++ 18 files changed, 315 insertions(+), 15 deletions(-) create mode 100644 src/components/modals/ModalView.jsx create mode 100644 src/components/modals/ModalView.module.css create mode 100644 src/components/modals/ModalsView.jsx create mode 100644 src/components/modals/context-menu/ContextMenu.jsx create mode 100644 src/components/modals/context-menu/ContextMenu.module.css rename src/components/windows/{Window.jsx => WindowView.jsx} (96%) rename src/components/windows/{Window.module.css => WindowView.module.css} (89%) create mode 100644 src/features/modals/modal.js create mode 100644 src/features/modals/modals.js create mode 100644 src/hooks/modals/ModalsContext.js diff --git a/src/App.jsx b/src/App.jsx index 68d515d..b704074 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -5,8 +5,22 @@ import { WindowsView } from "./components/windows/WindowsView.jsx"; import { VirtualRootProvider } from "./hooks/virtual-drive/VirtualRootContext.js"; import { Desktop } from "./components/desktop/Desktop.jsx"; import { SettingsProvider } from "./hooks/settings/SettingsContext.js"; +import { ModalsView } from "./components/modals/ModalsView.jsx"; +import { useEffect } from "react"; function App() { + useEffect(() => { + const onContextMenu = (event) => { + event.preventDefault(); + }; + + document.addEventListener("contextmenu", onContextMenu); + + return () => { + document.removeEventListener("contextmenu", onContextMenu); + }; + }, []); + return ( @@ -14,6 +28,7 @@ function App() {
+
diff --git a/src/App.module.css b/src/App.module.css index 0be3495..9b79409 100644 --- a/src/App.module.css +++ b/src/App.module.css @@ -6,4 +6,9 @@ height: 100%; text-align: center; overflow: hidden; + pointer-events: none; +} + +.App > * { + pointer-events: auto; } diff --git a/src/components/applications/calculator/Calculator.jsx b/src/components/applications/calculator/Calculator.jsx index cfff69e..0ef4897 100644 --- a/src/components/applications/calculator/Calculator.jsx +++ b/src/components/applications/calculator/Calculator.jsx @@ -1,4 +1,4 @@ -import styles from "./Calculator.module.css"; +// import styles from "./Calculator.module.css"; export function Calculator() { return ( diff --git a/src/components/desktop/Desktop.jsx b/src/components/desktop/Desktop.jsx index 53d6138..744e02f 100644 --- a/src/components/desktop/Desktop.jsx +++ b/src/components/desktop/Desktop.jsx @@ -3,10 +3,16 @@ import { SettingsManager } from "../../features/settings/settings.js"; import { useSettings } from "../../hooks/settings/SettingsContext.js"; import styles from "./Desktop.module.css"; import { useEffect } from "react"; +import { useModals } from "../../hooks/modals/ModalsContext.js"; +import { ModalsView } from "../modals/ModalsView.jsx"; +import Vector2 from "../../features/math/vector2.js"; +import { ContextMenu } from "../modals/context-menu/ContextMenu.jsx"; +import Modal from "../../features/modals/modal.js"; export function Desktop() { const settingsManager = useSettings(); const [wallpaper, setWallpaper] = useState(null); + const [modalsManager, modals] = useModals(); useEffect(() => { (async () => { @@ -15,7 +21,14 @@ export function Desktop() { })(); }, [settingsManager]); - return ( -
- ); + const onContextMenu = (event) => { + event.preventDefault(); + modalsManager.open(new Modal(ContextMenu).setPosition(new Vector2(event.clientX, event.clientY))); + }; + + return (<> +
+ +
+ ); } \ No newline at end of file diff --git a/src/components/modals/ModalView.jsx b/src/components/modals/ModalView.jsx new file mode 100644 index 0000000..db21615 --- /dev/null +++ b/src/components/modals/ModalView.jsx @@ -0,0 +1,17 @@ +import { Modal as ModalType } from "../../features/modals/modal.js"; +import OutsideClickListener from "../../hooks/utils/outsideClick.js"; +import styles from "./ModalView.module.css"; + +/** + * @param {object} root + * @param {ModalType} root.modal + */ +export function ModalView({ modal }) { + return ( + { modal.close(); }}> +
+ +
+
+ ); +} \ No newline at end of file diff --git a/src/components/modals/ModalView.module.css b/src/components/modals/ModalView.module.css new file mode 100644 index 0000000..8863b1a --- /dev/null +++ b/src/components/modals/ModalView.module.css @@ -0,0 +1,14 @@ +.Container { + --position-x: 0; + --position-y: 0; + + position: fixed; + top: calc(var(--position-y) * 1px); + left: calc(var(--position-x) * 1px); +} + +.Container > * { + background-color: var(--background-color-a); + border-radius: 0.5rem; + box-shadow: var(--window-box-shadow); +} \ No newline at end of file diff --git a/src/components/modals/ModalsView.jsx b/src/components/modals/ModalsView.jsx new file mode 100644 index 0000000..3b29b11 --- /dev/null +++ b/src/components/modals/ModalsView.jsx @@ -0,0 +1,18 @@ +import { Modal } from "../../features/modals/modal.js"; +import ModalsManager from "../../features/modals/modals.js"; +import { ModalView } from "./ModalView.jsx"; + +/** + * @param {object} root + * @param {ModalsManager} root.modalsManager + * @param {Modal[]} root.modals + */ +export function ModalsView({ modalsManager, modals }) { + return ( +
+ {modals?.map((modal) => + + )} +
+ ); +} \ No newline at end of file diff --git a/src/components/modals/context-menu/ContextMenu.jsx b/src/components/modals/context-menu/ContextMenu.jsx new file mode 100644 index 0000000..dff6eea --- /dev/null +++ b/src/components/modals/context-menu/ContextMenu.jsx @@ -0,0 +1,5 @@ +import styles from "./ContextMenu.module.css"; + +export function ContextMenu() { + return

{ event.preventDefault(); }}>This is a context menu!

; +} \ No newline at end of file diff --git a/src/components/modals/context-menu/ContextMenu.module.css b/src/components/modals/context-menu/ContextMenu.module.css new file mode 100644 index 0000000..ca3a344 --- /dev/null +++ b/src/components/modals/context-menu/ContextMenu.module.css @@ -0,0 +1,5 @@ +.Container { + margin: 0; + padding: 0.5rem; + border-top-left-radius: 0; +} \ No newline at end of file diff --git a/src/components/windows/Window.jsx b/src/components/windows/WindowView.jsx similarity index 96% rename from src/components/windows/Window.jsx rename to src/components/windows/WindowView.jsx index b71cfea..f23f60b 100644 --- a/src/components/windows/Window.jsx +++ b/src/components/windows/WindowView.jsx @@ -1,5 +1,5 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import styles from "./Window.module.css"; +import styles from "./WindowView.module.css"; import { faMinus, faWindowMaximize as fasWindowMaximize, faXmark } from "@fortawesome/free-solid-svg-icons"; import { ReactSVG } from "react-svg"; import { useWindowsManager } from "../../hooks/windows/WindowsManagerContext.js"; @@ -20,7 +20,7 @@ import utilStyles from "../../styles/utils.module.css"; * @param {Function} props.onInteract * @param {object} props.options */ -export const Window = memo(function Window({ id, app, size, position, focused = false, onInteract, options }) { +export const WindowView = memo(function Window({ id, app, size, position, focused = false, onInteract, options }) { const windowsManager = useWindowsManager(); const nodeRef = useRef(null); diff --git a/src/components/windows/Window.module.css b/src/components/windows/WindowView.module.css similarity index 89% rename from src/components/windows/Window.module.css rename to src/components/windows/WindowView.module.css index bead889..a4946b6 100644 --- a/src/components/windows/Window.module.css +++ b/src/components/windows/WindowView.module.css @@ -4,10 +4,6 @@ } .Window-container { - --shadow-size: 0.3rem; - --shadow-opacity: 35%; - --shadow-spread: 3; - position: absolute; display: flex; flex-direction: column; @@ -15,7 +11,7 @@ min-height: 150px; background-color: var(--background-color-c); border-radius: 0.5rem; - box-shadow: calc(var(--shadow-size) * -1) var(--shadow-size) calc(var(--shadow-size) * var(--shadow-spread)) 0px rgba(0, 0, 0, var(--shadow-opacity)); + box-shadow: var(--window-box-shadow); resize: both; overflow: hidden; } diff --git a/src/components/windows/WindowsView.jsx b/src/components/windows/WindowsView.jsx index 39b88fa..70256ea 100644 --- a/src/components/windows/WindowsView.jsx +++ b/src/components/windows/WindowsView.jsx @@ -1,7 +1,7 @@ -import { Window } from "./Window.jsx"; import { useWindows } from "../../hooks/windows/WindowsContext.js"; import { useWindowsManager } from "../../hooks/windows/WindowsManagerContext.js"; import { useEffect, useState } from "react"; +import { WindowView } from "./WindowView.jsx"; export function WindowsView() { const windows = useWindows(); @@ -18,7 +18,7 @@ export function WindowsView() { return (
{sortedWindows.map(({ id, app, size, position, options }) => - { windowsManager.focus(id); }} id={id} key={id} diff --git a/src/features/applications/applications.js b/src/features/applications/applications.js index 93a40ce..d79a078 100644 --- a/src/features/applications/applications.js +++ b/src/features/applications/applications.js @@ -5,7 +5,7 @@ import { WebView } from "../../components/applications/.templates/WebView.jsx"; import { Terminal } from "../../components/applications/terminal/Terminal.jsx"; import { TextEditor } from "../../components/applications/text-editor/TextEditor.jsx"; import { Settings } from "../../components/applications/settings/Settings.jsx"; -import { Calculator } from "../../components/applications/calculator/Calculator.jsx"; +// import { Calculator } from "../../components/applications/calculator/Calculator.jsx"; import Vector2 from "../math/vector2.js"; export default class ApplicationsManager { diff --git a/src/features/modals/modal.js b/src/features/modals/modal.js new file mode 100644 index 0000000..6a0fece --- /dev/null +++ b/src/features/modals/modal.js @@ -0,0 +1,99 @@ +import Vector2 from "../math/vector2.js"; +import ModalsManager from "./modals.js"; + +export default class Modal { + /** @type {Vector2} */ + size = new Vector2(400, 200); + /** @type {Vector2} */ + position = new Vector2(300, 300); + /** @type {string | null} */ + icon = null; + /** @type {title | null} */ + title = null; + /** @type {ModalsManager} */ + modalsManager = null; + /** @type {import("react").ReactElement | null} */ + element = null; + /** @type {object} */ + props = {}; + /** @type {Function | null} */ + callback = null; + /** @type {number | null} */ + id = null; + /** @type {boolean} */ + closeOnOutsideClick = true; + + /** + * @param {import("react").ReactElement} element + * @param {Function} callback + */ + constructor(element, callback) { + this.element = element; + this.callback = callback; + this.focus(); + } + + /** + * @param {string} icon + * @returns {Modal} + */ + setIcon(icon) { + this.icon = icon; + return this; + } + + /** + * @param {string} title + * @returns {Modal} + */ + setTitle(title) { + this.title = title; + return this; + } + + /** + * @param {Vector2} position + * @returns {Modal} + */ + setPosition(position) { + this.position = position; + return this; + } + + /** + * @param {object} props + * @returns {Modal} + */ + setProps(props) { + this.props = props; + return this; + } + + /** + * @param {boolean} closeOnOutsideClick + * @returns {Modal} + */ + setCloseOnOutsideClick(closeOnOutsideClick) { + this.closeOnOutsideClick = closeOnOutsideClick; + return this; + } + + focus() { + this.lastInteraction = new Date().valueOf(); + } + + /** + * @param {...any} args + */ + finish(...args) { + if (this.modalsManager == null || this.id == null) + return; + + this.modalsManager.close(this.id); + this.callback?.(...args); + } + + close() { + this.finish(); + } +} \ No newline at end of file diff --git a/src/features/modals/modals.js b/src/features/modals/modals.js new file mode 100644 index 0000000..777ba06 --- /dev/null +++ b/src/features/modals/modals.js @@ -0,0 +1,86 @@ +import Modal from "./modal.js"; + +export default class ModalsManager { + /** + * @type {Modal} + */ + modals = {}; + + /** + * @type {Function} + */ + updateModals = () => {}; + + /** + * @param {Modal} modal + * @param {boolean} closeOthers + */ + open(modal, closeOthers = true) { + if (closeOthers) { + this.modalIds.forEach((id) => { + this.close(id, false); + }); + } + + let id = 0; + while (this.modalIds.includes(id.toString())) { + id++; + } + + modal.id = id; + modal.modalsManager = this; + + this.modals[id] = modal; + this.updateModals(this.modals); + } + + /** + * @param {string} modalId + * @param {boolean} sendModalsUpdate + */ + close(modalId, sendModalsUpdate = true) { + modalId = modalId.toString(); + + if (!this.modalIds.includes(modalId)) { + console.log(`Failed to close modal ${modalId}: modal not found`); + return; + } + + console.log(`Closing window ${modalId}`); + delete this.modals[modalId]; + + if (sendModalsUpdate) + this.updateModals(this.modals); + } + + /** + * @param {string} modalId + */ + focus(modalId) { + modalId = modalId.toString(); + + if (!this.modalIds.includes(modalId)) { + console.log(`Failed to focus modal ${modalId}: modal not found`); + return; + } + + const modal = this.modals[modalId]; + modal.focus(); + + this.updateModals(this.modals); + } + + /** + * @param {Function} updateModals + */ + setUpdateModals(updateModals) { + this.updateModals = updateModals; + } + + /** + * @returns {string[]} + */ + get modalIds() { + return Object.keys(this.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..bdcb67f --- /dev/null +++ b/src/hooks/modals/ModalsContext.js @@ -0,0 +1,19 @@ +import { useCallback, useState } from "react"; +import ModalsManager from "../../features/modals/modals.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/index.js b/src/index.js index dda47e4..0e3cc9d 100644 --- a/src/index.js +++ b/src/index.js @@ -7,7 +7,7 @@ import reportWebVitals from "./reportWebVitals"; const root = ReactDOM.createRoot(document.getElementById("root")); root.render( - + ); diff --git a/src/styles/global.css b/src/styles/global.css index d0ba01c..5b83c60 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -37,11 +37,19 @@ --background-color-d: var(--dark-grey-e); --scrollbar-color: hsla(211, 29%, 40%, 25%); + + --window-shadow-size: 0.3rem; + --window-shadow-opacity: 35%; + --window-shadow-spread: 3; + --window-box-shadow: calc(var(--window-shadow-size) * -1) var(--window-shadow-size) + calc(var(--window-shadow-size) * var(--window-shadow-spread)) 0px + rgba(0, 0, 0, var(--window-shadow-opacity)); } html, body, #root { width: 100%; height: 100%; + pointer-events: none; } html { From 72165ad7704fb21af34874370162bf1c1e93d278 Mon Sep 17 00:00:00 2001 From: Prozilla Date: Fri, 11 Aug 2023 15:43:54 +0200 Subject: [PATCH 2/6] Added context menu modal --- .../applications/settings/Settings.jsx | 7 +++-- src/components/desktop/Desktop.jsx | 11 ++++++- .../modals/context-menu/ContextMenu.jsx | 25 ++++++++++++++-- .../context-menu/ContextMenu.module.css | 29 ++++++++++++++++++- src/features/modals/modals.js | 2 +- 5 files changed, 67 insertions(+), 7 deletions(-) diff --git a/src/components/applications/settings/Settings.jsx b/src/components/applications/settings/Settings.jsx index 164f1cd..97d5c0e 100644 --- a/src/components/applications/settings/Settings.jsx +++ b/src/components/applications/settings/Settings.jsx @@ -108,8 +108,11 @@ function AboutTab({ windowsManager, virtualRoot }) { ); } -export function Settings() { - const [tabIndex, setTabIndex] = useState(0); +/** + * @param {number} param0 + */ +export function Settings({ initialTabIndex }) { + const [tabIndex, setTabIndex] = useState(initialTabIndex ?? 0); const virtualRoot = useVirtualRoot(); const settingsManager = useSettings(); const windowsManager = useWindowsManager(); diff --git a/src/components/desktop/Desktop.jsx b/src/components/desktop/Desktop.jsx index 744e02f..9901108 100644 --- a/src/components/desktop/Desktop.jsx +++ b/src/components/desktop/Desktop.jsx @@ -8,11 +8,13 @@ import { ModalsView } from "../modals/ModalsView.jsx"; import Vector2 from "../../features/math/vector2.js"; import { ContextMenu } from "../modals/context-menu/ContextMenu.jsx"; import Modal from "../../features/modals/modal.js"; +import { useWindowsManager } from "../../hooks/windows/WindowsManagerContext.js"; export function Desktop() { const settingsManager = useSettings(); const [wallpaper, setWallpaper] = useState(null); const [modalsManager, modals] = useModals(); + const windowsManager = useWindowsManager(); useEffect(() => { (async () => { @@ -23,7 +25,14 @@ export function Desktop() { const onContextMenu = (event) => { event.preventDefault(); - modalsManager.open(new Modal(ContextMenu).setPosition(new Vector2(event.clientX, event.clientY))); + modalsManager.open(new Modal(ContextMenu) + .setPosition(new Vector2(event.clientX, event.clientY)) + .setProps({ + options: { + "Change appearance": () => { windowsManager.open("settings", { initialTabIndex: 0 }); }, + "Open in Files": () => { windowsManager.open("file-explorer", { startPath: "~/Desktop" }); } + } + })); }; return (<> diff --git a/src/components/modals/context-menu/ContextMenu.jsx b/src/components/modals/context-menu/ContextMenu.jsx index dff6eea..bf001d8 100644 --- a/src/components/modals/context-menu/ContextMenu.jsx +++ b/src/components/modals/context-menu/ContextMenu.jsx @@ -1,5 +1,26 @@ +import Modal from "../../../features/modals/modal.js"; +import { formatShortcut } from "../../../features/utils/string.js"; import styles from "./ContextMenu.module.css"; -export function ContextMenu() { - return

{ event.preventDefault(); }}>This is a context menu!

; +/** + * @param {object} props + * @param {Modal} props.modal + * @param {Object} props.options + * @param {Object} props.shortcuts + */ +export function ContextMenu({ modal, options, shortcuts }) { + return (
+ {Object.entries(options).map(([label, callback]) => + + )} +
); } \ No newline at end of file diff --git a/src/components/modals/context-menu/ContextMenu.module.css b/src/components/modals/context-menu/ContextMenu.module.css index ca3a344..6f91741 100644 --- a/src/components/modals/context-menu/ContextMenu.module.css +++ b/src/components/modals/context-menu/ContextMenu.module.css @@ -1,5 +1,32 @@ .Container { - margin: 0; padding: 0.5rem; border-top-left-radius: 0; +} + +.Button { + display: flex; + gap: 0.75rem; + justify-content: space-between; + width: 100%; + padding: 0.25rem 0.5rem; + background: none; + border: none; + border-radius: 0.5rem; + outline: none; + font-size: 0.875rem; + text-align: start; + white-space: nowrap; + cursor: pointer; +} + +.Button:hover, .Button:focus-visible { + background-color: rgba(255, 255, 255, 5%); +} + +.Label { + margin: 0; +} + +.Shortcut { + color: var(--foreground-color-b); } \ No newline at end of file diff --git a/src/features/modals/modals.js b/src/features/modals/modals.js index 777ba06..99cce7f 100644 --- a/src/features/modals/modals.js +++ b/src/features/modals/modals.js @@ -46,7 +46,7 @@ export default class ModalsManager { return; } - console.log(`Closing window ${modalId}`); + console.log(`Closing modal ${modalId}`); delete this.modals[modalId]; if (sendModalsUpdate) From 7510d1bbf48ef68de5dc2fa6f8c87ee45f3c1c35 Mon Sep 17 00:00:00 2001 From: Prozilla Date: Fri, 11 Aug 2023 16:27:12 +0200 Subject: [PATCH 3/6] Added context menu hook --- .../applications/.common/HeaderMenu.jsx | 50 ++----------- .../applications/text-editor/TextEditor.jsx | 4 +- src/components/desktop/Desktop.jsx | 25 +++---- src/components/modals/ModalsView.jsx | 7 +- src/components/modals/ModalsView.module.css | 3 + .../modals/context-menu/ContextMenu.jsx | 2 +- .../context-menu/ContextMenu.module.css | 3 +- src/components/windows/WindowView.jsx | 25 +++++-- src/hooks/modals/ContextMenu.js | 24 +++++++ .../modals/{ModalsContext.js => Modals.js} | 0 src/hooks/utils/keyboard.js | 71 ++++++++++++++++++- 11 files changed, 140 insertions(+), 74 deletions(-) create mode 100644 src/components/modals/ModalsView.module.css create mode 100644 src/hooks/modals/ContextMenu.js rename src/hooks/modals/{ModalsContext.js => Modals.js} (100%) diff --git a/src/components/applications/.common/HeaderMenu.jsx b/src/components/applications/.common/HeaderMenu.jsx index 627f338..eb800b7 100644 --- a/src/components/applications/.common/HeaderMenu.jsx +++ b/src/components/applications/.common/HeaderMenu.jsx @@ -1,56 +1,14 @@ -import { useState } from "react"; -import { removeFromArray } from "../../../features/utils/array.js"; -import { useKeyboardListener } from "../../../hooks/utils/keyboard.js"; +import { useShortcuts } from "../../../hooks/utils/keyboard.js"; import { DropdownButton } from "../../utils/DropdownButton.jsx"; import styles from "./HeaderMenu.module.css"; /** * @param {Object} props - * @param {Object>} props.options - * @param {Object>} props.shortcuts + * @param {Object>} props.options + * @param {Object>} props.shortcuts */ export function HeaderMenu({ options, shortcuts }) { - const [activeKeys, setActiveKeys] = useState([]); - - const checkShortcuts = (event, allowExecution = true) => { - for (const [category, group] of Object.entries(shortcuts)) { - for (const [name, shortcut] of Object.entries(group)) { - let active = true; - - shortcut.forEach((key) => { - if (!activeKeys.includes(key) && event.key != key) - return active = false; - }); - - if (active) { - event.preventDefault(); - if (shortcut.includes(event.key) && allowExecution) { - options?.[category]?.[name]?.(); - } - } - } - } - } - - const onKeyDown = (event) => { - const isRepeated = activeKeys.includes(event.key); - checkShortcuts(event, isRepeated); - - if (!isRepeated) - setActiveKeys(activeKeys.concat([event.key])); - }; - - const onKeyUp = (event) => { - checkShortcuts(event); - - if (activeKeys.includes(event.key)) { - const keys = [...activeKeys]; - removeFromArray(event.key, keys); - setActiveKeys(keys); - } - }; - - useKeyboardListener({ onKeyDown, onKeyUp }); + useShortcuts({ options, shortcuts }); return (
diff --git a/src/components/applications/text-editor/TextEditor.jsx b/src/components/applications/text-editor/TextEditor.jsx index c084648..a17d4e8 100644 --- a/src/components/applications/text-editor/TextEditor.jsx +++ b/src/components/applications/text-editor/TextEditor.jsx @@ -93,7 +93,7 @@ export function TextEditor({ file, setTitle, close, mode, app }) { "New": newText, "Save": saveText, // "Save As": saveTextAs, - "Exit": () => { + "Quit": () => { close(); }, }, @@ -116,7 +116,7 @@ export function TextEditor({ file, setTitle, close, mode, app }) { "File": { "New": ["Control", "e"], "Save": ["Control", "s"], - "Exit": ["Control", "x"], + "Quit": ["Control", "q"], }, "View": { "Zoom In": ["Control", "+"], diff --git a/src/components/desktop/Desktop.jsx b/src/components/desktop/Desktop.jsx index 9901108..f6ce0e5 100644 --- a/src/components/desktop/Desktop.jsx +++ b/src/components/desktop/Desktop.jsx @@ -3,18 +3,23 @@ import { SettingsManager } from "../../features/settings/settings.js"; import { useSettings } from "../../hooks/settings/SettingsContext.js"; import styles from "./Desktop.module.css"; import { useEffect } from "react"; -import { useModals } from "../../hooks/modals/ModalsContext.js"; +import { useModals } from "../../hooks/modals/Modals.js"; import { ModalsView } from "../modals/ModalsView.jsx"; -import Vector2 from "../../features/math/vector2.js"; -import { ContextMenu } from "../modals/context-menu/ContextMenu.jsx"; -import Modal from "../../features/modals/modal.js"; import { useWindowsManager } from "../../hooks/windows/WindowsManagerContext.js"; +import { useContextMenu } from "../../hooks/modals/ContextMenu.js"; export function Desktop() { const settingsManager = useSettings(); const [wallpaper, setWallpaper] = useState(null); const [modalsManager, modals] = useModals(); const windowsManager = useWindowsManager(); + const { onContextMenu } = useContextMenu({ + modalsManager, + options: { + "Change appearance": () => { windowsManager.open("settings", { initialTabIndex: 0 }); }, + "Open in Files": () => { windowsManager.open("file-explorer", { startPath: "~/Desktop" }); } + } + }); useEffect(() => { (async () => { @@ -23,18 +28,6 @@ export function Desktop() { })(); }, [settingsManager]); - const onContextMenu = (event) => { - event.preventDefault(); - modalsManager.open(new Modal(ContextMenu) - .setPosition(new Vector2(event.clientX, event.clientY)) - .setProps({ - options: { - "Change appearance": () => { windowsManager.open("settings", { initialTabIndex: 0 }); }, - "Open in Files": () => { windowsManager.open("file-explorer", { startPath: "~/Desktop" }); } - } - })); - }; - return (<>
diff --git a/src/components/modals/ModalsView.jsx b/src/components/modals/ModalsView.jsx index 3b29b11..5285dc9 100644 --- a/src/components/modals/ModalsView.jsx +++ b/src/components/modals/ModalsView.jsx @@ -1,15 +1,18 @@ import { Modal } from "../../features/modals/modal.js"; import ModalsManager from "../../features/modals/modals.js"; import { ModalView } from "./ModalView.jsx"; +import styles from "./ModalsView.module.css"; /** * @param {object} root * @param {ModalsManager} root.modalsManager * @param {Modal[]} root.modals + * @param {import("react").CSSProperties} root.style + * @param {import("react").className} root.className */ -export function ModalsView({ modalsManager, modals }) { +export function ModalsView({ modalsManager, modals, style, className }) { return ( -
+
{modals?.map((modal) => )} diff --git a/src/components/modals/ModalsView.module.css b/src/components/modals/ModalsView.module.css new file mode 100644 index 0000000..0197d6b --- /dev/null +++ b/src/components/modals/ModalsView.module.css @@ -0,0 +1,3 @@ +.Container { + position: relative; +} \ No newline at end of file diff --git a/src/components/modals/context-menu/ContextMenu.jsx b/src/components/modals/context-menu/ContextMenu.jsx index bf001d8..35e6b3e 100644 --- a/src/components/modals/context-menu/ContextMenu.jsx +++ b/src/components/modals/context-menu/ContextMenu.jsx @@ -11,7 +11,7 @@ import styles from "./ContextMenu.module.css"; export function ContextMenu({ modal, options, shortcuts }) { return (
{Object.entries(options).map(([label, callback]) => -