Added actions
This commit is contained in:
parent
d73db00326
commit
5263cfd2b6
26 changed files with 460 additions and 209 deletions
|
|
@ -63,7 +63,7 @@
|
||||||
"default-case": "off",
|
"default-case": "off",
|
||||||
"arrow-parens": "error",
|
"arrow-parens": "error",
|
||||||
"space-infix-ops": "warn",
|
"space-infix-ops": "warn",
|
||||||
"react/no-multi-comp": "error",
|
"react/no-multi-comp": ["error", { "ignoreStateless": true }],
|
||||||
"jsdoc/no-undefined-types": "warn",
|
"jsdoc/no-undefined-types": "warn",
|
||||||
"jsdoc/require-param": "warn",
|
"jsdoc/require-param": "warn",
|
||||||
"jsdoc/check-tag-names": "warn",
|
"jsdoc/check-tag-names": "warn",
|
||||||
|
|
|
||||||
121
src/components/actions/Actions.jsx
Normal file
121
src/components/actions/Actions.jsx
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
import { Children, cloneElement, isValidElement, useEffect, useRef, useState } from "react";
|
||||||
|
import { useShortcuts } from "../../hooks/utils/keyboard.js";
|
||||||
|
import style from "./Actions.module.css";
|
||||||
|
import { useScreenDimensions } from "../../hooks/utils/screen.js";
|
||||||
|
import { TASK_BAR_HEIGHT } from "../../constants/taskBar.js";
|
||||||
|
|
||||||
|
export const STYLES = {
|
||||||
|
CONTEXT_MENU: style["Context-menu"],
|
||||||
|
SHORTCUTS_LISTENER: style["Shortcuts-listener"],
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @callback actionsType
|
||||||
|
* @param {object} props
|
||||||
|
* @param {string=} props.className
|
||||||
|
* @param {Function} props.onAnyTrigger
|
||||||
|
* @param {import("react").ElementType} props.children
|
||||||
|
* @param {*} props.triggerParams
|
||||||
|
* @param {boolean} props.avoidTaskBar
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {actionsType}
|
||||||
|
* @example
|
||||||
|
* <ClickAction
|
||||||
|
* label="Reload"
|
||||||
|
* shortcut={["Control", "r"]}
|
||||||
|
* icon={faArrowsRotate}
|
||||||
|
* onTrigger={() => {
|
||||||
|
* reloadViewport();
|
||||||
|
* }}
|
||||||
|
* />
|
||||||
|
*/
|
||||||
|
export function Actions({ children, className, onAnyTrigger, triggerParams, avoidTaskBar = true }) {
|
||||||
|
const isListener = (className === STYLES.SHORTCUTS_LISTENER);
|
||||||
|
|
||||||
|
const ref = useRef(null);
|
||||||
|
const [initiated, setInitiated] = useState(false);
|
||||||
|
const [alignLeft, setAlignLeft] = useState(false);
|
||||||
|
const [alignTop, setAlignTop] = useState(false);
|
||||||
|
const [screenWidth, screenHeight] = useScreenDimensions();
|
||||||
|
|
||||||
|
const options = {};
|
||||||
|
const shortcuts = {};
|
||||||
|
|
||||||
|
const iterateOverChildren = (children) => {
|
||||||
|
let actionId = 0;
|
||||||
|
const newChildren = Children.map(children, (child) => {
|
||||||
|
if (!isValidElement(child))
|
||||||
|
return child;
|
||||||
|
|
||||||
|
actionId++;
|
||||||
|
|
||||||
|
const { label, shortcut, onTrigger } = child.props;
|
||||||
|
if (label != null && onTrigger != null) {
|
||||||
|
options[actionId] = onTrigger;
|
||||||
|
|
||||||
|
if (shortcut != null)
|
||||||
|
shortcuts[actionId] = shortcut;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isListener) {
|
||||||
|
iterateOverChildren(child.props.children);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cloneElement(child, {
|
||||||
|
...child.props,
|
||||||
|
actionId,
|
||||||
|
children: iterateOverChildren(child.props.children),
|
||||||
|
onTrigger: (event) => {
|
||||||
|
onAnyTrigger?.(event, triggerParams);
|
||||||
|
onTrigger?.(event, triggerParams);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return newChildren;
|
||||||
|
};
|
||||||
|
|
||||||
|
useShortcuts({ options, shortcuts, useCategories: false });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (ref.current == null || screenWidth == null || screenHeight == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const rect = ref.current.getBoundingClientRect();
|
||||||
|
const maxX = screenWidth;
|
||||||
|
let maxY = screenHeight;
|
||||||
|
|
||||||
|
if (avoidTaskBar)
|
||||||
|
maxY -= TASK_BAR_HEIGHT;
|
||||||
|
|
||||||
|
const isOverflowingRight = (rect.x + rect.width > maxX);
|
||||||
|
const isOverflowingBottom = (rect.y + rect.height > maxY);
|
||||||
|
|
||||||
|
if (isOverflowingRight)
|
||||||
|
setAlignLeft(true);
|
||||||
|
if (isOverflowingBottom)
|
||||||
|
setAlignTop(true);
|
||||||
|
|
||||||
|
setInitiated(true);
|
||||||
|
}, [alignLeft, avoidTaskBar, screenHeight, screenWidth]);
|
||||||
|
|
||||||
|
if (isListener)
|
||||||
|
return iterateOverChildren(children);
|
||||||
|
|
||||||
|
const classNames = [style.Container];
|
||||||
|
if (className != null)
|
||||||
|
classNames.push(className);
|
||||||
|
if (alignLeft)
|
||||||
|
classNames.push(style["Align-left"]);
|
||||||
|
if (alignTop)
|
||||||
|
classNames.push(style["Align-top"]);
|
||||||
|
if (!initiated)
|
||||||
|
classNames.push(style.Uninitiated);
|
||||||
|
|
||||||
|
return (<div ref={ref} className={classNames.join(" ")}>
|
||||||
|
{iterateOverChildren(children)}
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
91
src/components/actions/Actions.module.css
Normal file
91
src/components/actions/Actions.module.css
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
.Container {
|
||||||
|
/* These variables describe the position of the container relative to top left corner of its parent */
|
||||||
|
--left: 0;
|
||||||
|
--top: 0;
|
||||||
|
--right: calc(1 - var(--left));
|
||||||
|
--bottom: calc(1 - var(--top));
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity 100ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Container.Uninitiated {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Container.Align-left {
|
||||||
|
--left: 1;
|
||||||
|
|
||||||
|
left: unset;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Container.Align-top {
|
||||||
|
--top: 1;
|
||||||
|
|
||||||
|
top: unset;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Context-menu.Container {
|
||||||
|
--border-radius: 0.5rem;
|
||||||
|
|
||||||
|
padding: 0.375rem;
|
||||||
|
border-top-left-radius: calc((1 - var(--right) * var(--bottom)) * var(--border-radius)) !important;
|
||||||
|
border-top-right-radius: calc((1 - var(--left) * var(--bottom)) * var(--border-radius)) !important;
|
||||||
|
border-bottom-left-radius: calc((1 - var(--right) * var(--top)) * var(--border-radius)) !important;
|
||||||
|
border-bottom-right-radius: calc((1 - var(--left) * var(--top)) * var(--border-radius)) !important;
|
||||||
|
background-color: var(--background-color-b) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Context-menu .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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Context-menu .Button:hover,
|
||||||
|
.Context-menu .Button:focus-visible {
|
||||||
|
background-color: hsla(var(--background-color-a-hsl), 75%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Context-menu .Label {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Context-menu .Label p,
|
||||||
|
.Context-menu .Shortcut {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Context-menu .Icon {
|
||||||
|
display: flex;
|
||||||
|
width: 0.875rem;
|
||||||
|
height: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Context-menu .Icon > svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Context-menu .Shortcut {
|
||||||
|
color: var(--foreground-color-b);
|
||||||
|
}
|
||||||
0
src/components/actions/actions/Action.module.css
Normal file
0
src/components/actions/actions/Action.module.css
Normal file
13
src/components/actions/actions/ClickAction.jsx
Normal file
13
src/components/actions/actions/ClickAction.jsx
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { formatShortcut } from "../../../features/utils/string.js";
|
||||||
|
import styles from "../Actions.module.css";
|
||||||
|
|
||||||
|
export function ClickAction({ actionId, label, shortcut, onTrigger, icon }) {
|
||||||
|
return (<button key={actionId} className={styles.Button} tabIndex={0} onClick={onTrigger}>
|
||||||
|
<span className={styles.Label}>
|
||||||
|
{icon && <div className={styles.Icon}><FontAwesomeIcon icon={icon}/></div>}
|
||||||
|
<p>{label}</p>
|
||||||
|
</span>
|
||||||
|
{shortcut && <p className={styles.Shortcut}>{formatShortcut(shortcut)}</p>}
|
||||||
|
</button>);
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@ import { useState } from "react";
|
||||||
import { useVirtualRoot } from "../../../hooks/virtual-drive/virtualRootContext.js";
|
import { useVirtualRoot } from "../../../hooks/virtual-drive/virtualRootContext.js";
|
||||||
import styles from "./FileExplorer.module.css";
|
import styles from "./FileExplorer.module.css";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { faArrowUp, faCaretLeft, faCaretRight, faCog, faDesktop, faFileLines, faHouse, faImage, faPlus, faSearch } from "@fortawesome/free-solid-svg-icons";
|
import { faArrowUp, faCaretLeft, faCaretRight, faCog, faDesktop, faFileLines, faHouse, faImage, faPlus, faSearch, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { useWindowsManager } from "../../../hooks/windows/windowsManagerContext.js";
|
import { useWindowsManager } from "../../../hooks/windows/windowsManagerContext.js";
|
||||||
import { useContextMenu } from "../../../hooks/modals/contextMenu.js";
|
import { useContextMenu } from "../../../hooks/modals/contextMenu.js";
|
||||||
import { QuickAccessButton } from "./QuickAccessButton.jsx";
|
import { QuickAccessButton } from "./QuickAccessButton.jsx";
|
||||||
|
|
@ -10,6 +10,8 @@ import { useDialogBox } from "../../../hooks/modals/dialogBox.js";
|
||||||
import Vector2 from "../../../features/math/vector2.js";
|
import Vector2 from "../../../features/math/vector2.js";
|
||||||
import { DIALOG_CONTENT_TYPES } from "../../../constants/modals.js";
|
import { DIALOG_CONTENT_TYPES } from "../../../constants/modals.js";
|
||||||
import { DirectoryList } from "./directory-list/DirectoryList.jsx";
|
import { DirectoryList } from "./directory-list/DirectoryList.jsx";
|
||||||
|
import { Actions } from "../../actions/Actions.jsx";
|
||||||
|
import { ClickAction } from "../../actions/actions/ClickAction.jsx";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import("../../windows/WindowView.jsx").windowProps} props
|
* @param {import("../../windows/WindowView.jsx").windowProps} props
|
||||||
|
|
@ -21,19 +23,25 @@ export function FileExplorer({ startPath, app, modalsManager }) {
|
||||||
const windowsManager = useWindowsManager();
|
const windowsManager = useWindowsManager();
|
||||||
const [showHidden] = useState(true);
|
const [showHidden] = useState(true);
|
||||||
|
|
||||||
const { onContextMenu: onContextMenuFile } = useContextMenu({
|
const { onContextMenu: onContextMenuFile } = useContextMenu({ modalsManager, Actions: (props) =>
|
||||||
modalsManager,
|
<Actions {...props}>
|
||||||
options: {
|
<ClickAction label="Open" onTrigger={(event, file) => {
|
||||||
"Open": (file) => { file.open(windowsManager); },
|
file.open(windowsManager);
|
||||||
"Delete": (file) => { file.delete(); },
|
}}/>
|
||||||
}
|
<ClickAction label="Delete" icon={faTrash} onTrigger={(event, file) => {
|
||||||
|
file.delete();
|
||||||
|
}}/>
|
||||||
|
</Actions>
|
||||||
});
|
});
|
||||||
const { onContextMenu: onContextMenuFolder } = useContextMenu({
|
const { onContextMenu: onContextMenuFolder } = useContextMenu({ modalsManager, Actions: (props) =>
|
||||||
modalsManager,
|
<Actions {...props}>
|
||||||
options: {
|
<ClickAction label="Open" onTrigger={(event, folder) => {
|
||||||
"Open": (folder) => { changeDirectory(folder.name); },
|
changeDirectory(folder.name);
|
||||||
"Delete": (folder) => { folder.delete(); }
|
}}/>
|
||||||
}
|
<ClickAction label="Delete" icon={faTrash} onTrigger={(event, folder) => {
|
||||||
|
folder.delete();
|
||||||
|
}}/>
|
||||||
|
</Actions>
|
||||||
});
|
});
|
||||||
// const { onContextMenu: onNew } = useContextMenu({
|
// const { onContextMenu: onNew } = useContextMenu({
|
||||||
// modalsManager,
|
// modalsManager,
|
||||||
|
|
@ -50,7 +58,7 @@ export function FileExplorer({ startPath, app, modalsManager }) {
|
||||||
|
|
||||||
const directory = absolute ? virtualRoot.navigate(path) : currentDirectory.navigate(path);
|
const directory = absolute ? virtualRoot.navigate(path) : currentDirectory.navigate(path);
|
||||||
|
|
||||||
console.log(directory);
|
console.debug(directory);
|
||||||
|
|
||||||
if (directory) {
|
if (directory) {
|
||||||
setCurrentDirectory(directory);
|
setCurrentDirectory(directory);
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,7 @@ export function AboutSettings() {
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className={`${styles.Button} ${utilStyles["Text-bold"]}`}
|
className={`${styles.Button} ${utilStyles["Text-bold"]}`}
|
||||||
onClick={(event) => {
|
href="https://github.com/Prozilla/prozilla-os"
|
||||||
event.preventDefault();
|
|
||||||
window.open("https://github.com/Prozilla/prozilla-os");
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
View source
|
View source
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -131,28 +131,18 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.Button {
|
.Button {
|
||||||
--hover-color: var(--background-color-b);
|
--normal-color: var(--background-color-a) !important;
|
||||||
|
--hover-color: var(--background-color-b) !important;
|
||||||
|
|
||||||
margin-bottom: 0.75rem;
|
margin-bottom: 0.75rem !important;
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
color: var(--foreground-color-a);
|
|
||||||
background-color: var(--background-color-a);
|
|
||||||
border: none;
|
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
outline: none;
|
|
||||||
transition: background-color 100ms ease-in-out;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Button:hover, .Button:focus-visible {
|
|
||||||
background-color: var(--hover-color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Button-red {
|
.Button-red {
|
||||||
--hover-color: var(--red-b);
|
--text-color: var(--background-color-a) !important;
|
||||||
|
--normal-color: var(--red-a) !important;
|
||||||
color: var(--background-color-a);
|
--hover-color: var(--red-b) !important;
|
||||||
background-color: var(--red-a);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Progress-bar-container {
|
.Progress-bar-container {
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,9 @@ import { useVirtualRoot } from "../../hooks/virtual-drive/virtualRootContext.js"
|
||||||
import { DirectoryList } from "../applications/file-explorer/directory-list/DirectoryList.jsx";
|
import { DirectoryList } from "../applications/file-explorer/directory-list/DirectoryList.jsx";
|
||||||
import { APPS, APP_NAMES } from "../../constants/applications.js";
|
import { APPS, APP_NAMES } from "../../constants/applications.js";
|
||||||
import Vector2 from "../../features/math/vector2.js";
|
import Vector2 from "../../features/math/vector2.js";
|
||||||
|
import { Actions } from "../actions/Actions.jsx";
|
||||||
|
import { ClickAction } from "../actions/actions/ClickAction.jsx";
|
||||||
|
import { faArrowsRotate, faFolder, faPaintBrush, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
export const Desktop = memo(() => {
|
export const Desktop = memo(() => {
|
||||||
const settingsManager = useSettingsManager();
|
const settingsManager = useSettingsManager();
|
||||||
|
|
@ -21,35 +24,44 @@ export const Desktop = memo(() => {
|
||||||
const windowsManager = useWindowsManager();
|
const windowsManager = useWindowsManager();
|
||||||
const virtualRoot = useVirtualRoot();
|
const virtualRoot = useVirtualRoot();
|
||||||
|
|
||||||
const { onContextMenu } = useContextMenu({
|
const { onContextMenu, ShortcutsListener } = useContextMenu({ modalsManager, Actions: (props) =>
|
||||||
modalsManager,
|
<Actions {...props}>
|
||||||
options: {
|
<ClickAction label="Reload" shortcut={["Control", "r"]} icon={faArrowsRotate} onTrigger={() => {
|
||||||
"Refresh": () => { reloadViewport(); },
|
reloadViewport();
|
||||||
"Change appearance": () => { windowsManager.open("settings", { initialTabIndex: 0 }); },
|
}}/>
|
||||||
[`Open in ${APP_NAMES.FILE_EXPLORER}`]: () => {
|
<ClickAction label="Change appearance" icon={faPaintBrush} onTrigger={() => {
|
||||||
|
windowsManager.open("settings", { initialTabIndex: 0 });
|
||||||
|
}}/>
|
||||||
|
<ClickAction label={`Open in ${APP_NAMES.FILE_EXPLORER}`} icon={faFolder} onTrigger={() => {
|
||||||
windowsManager.open(APPS.FILE_EXPLORER, { startPath: "~/Desktop" });
|
windowsManager.open(APPS.FILE_EXPLORER, { startPath: "~/Desktop" });
|
||||||
},
|
}}/>
|
||||||
}
|
</Actions>
|
||||||
});
|
});
|
||||||
const { onContextMenu: onContextMenuFile } = useContextMenu({
|
const { onContextMenu: onContextMenuFile } = useContextMenu({ modalsManager, Actions: (props) =>
|
||||||
modalsManager,
|
<Actions {...props}>
|
||||||
options: {
|
<ClickAction label="Open" onTrigger={(event, file) => {
|
||||||
"Open": (file) => { file.open(windowsManager); },
|
file.open(windowsManager);
|
||||||
[`Reveal in ${APP_NAMES.FILE_EXPLORER}`]: (file) => {
|
}}/>
|
||||||
windowsManager.open(APPS.FILE_EXPLORER, { startPath: file.parent.path });
|
<ClickAction label={`Reveal in ${APP_NAMES.FILE_EXPLORER}`} icon={faFolder} onTrigger={(event, file) => {
|
||||||
},
|
file.parent.open(windowsManager);
|
||||||
"Delete": (file) => { file.delete(); },
|
}}/>
|
||||||
}
|
<ClickAction label="Delete" icon={faTrash} onTrigger={(event, file) => {
|
||||||
|
file.delete();
|
||||||
|
}}/>
|
||||||
|
</Actions>
|
||||||
});
|
});
|
||||||
const { onContextMenu: onContextMenuFolder } = useContextMenu({
|
const { onContextMenu: onContextMenuFolder } = useContextMenu({ modalsManager, Actions: (props) =>
|
||||||
modalsManager,
|
<Actions {...props}>
|
||||||
options: {
|
<ClickAction label="Open" onTrigger={(event, folder) => {
|
||||||
"Open": (folder) => { folder.open(windowsManager); },
|
folder.open(windowsManager);
|
||||||
[`Reveal in ${APP_NAMES.FILE_EXPLORER}`]: (folder) => {
|
}}/>
|
||||||
windowsManager.open(APPS.FILE_EXPLORER, { startPath: folder.parent.path });
|
<ClickAction label={`Reveal in ${APP_NAMES.FILE_EXPLORER}`} icon={faFolder} onTrigger={(event, folder) => {
|
||||||
},
|
folder.parent.open(windowsManager);
|
||||||
"Delete": (folder) => { folder.delete(); },
|
}}/>
|
||||||
}
|
<ClickAction label="Delete" icon={faTrash} onTrigger={(event, folder) => {
|
||||||
|
folder.delete();
|
||||||
|
}}/>
|
||||||
|
</Actions>
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -67,6 +79,7 @@ export const Desktop = memo(() => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
|
<ShortcutsListener/>
|
||||||
<div
|
<div
|
||||||
className={styles.Container}
|
className={styles.Container}
|
||||||
onContextMenu={onContextMenu}
|
onContextMenu={onContextMenu}
|
||||||
|
|
|
||||||
|
|
@ -24,15 +24,18 @@ export const ModalView = memo(({ modal }) => {
|
||||||
};
|
};
|
||||||
}, [modal]);
|
}, [modal]);
|
||||||
|
|
||||||
const container = (<div className={styles.Container} style={{ "--position-x": modal.position.x, "--position-y": modal.position.y }}>
|
const Container = () => (<div
|
||||||
|
className={styles.Container}
|
||||||
|
style={{ "--position-x": modal.position.x, "--position-y": modal.position.y }}
|
||||||
|
>
|
||||||
<modal.element modal={modal} {...modal.props}/>
|
<modal.element modal={modal} {...modal.props}/>
|
||||||
</div>);
|
</div>);
|
||||||
|
|
||||||
if (modal.dismissible) {
|
if (modal.dismissible) {
|
||||||
return (<OutsideClickListener onOutsideClick={() => { modal.close(); }}>
|
return (<OutsideClickListener onOutsideClick={() => { modal.close(); }}>
|
||||||
{container}
|
<Container/>
|
||||||
</OutsideClickListener>);
|
</OutsideClickListener>);
|
||||||
} else {
|
} else {
|
||||||
return container;
|
return <Container/>;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -11,7 +11,7 @@ import styles from "./ModalsView.module.css";
|
||||||
* @param {import("react").CSSProperties} root.style
|
* @param {import("react").CSSProperties} root.style
|
||||||
* @param {import("react").className} root.className
|
* @param {import("react").className} root.className
|
||||||
*/
|
*/
|
||||||
export const ModalsView = memo(({ modalsManager, modals, style, className }) => {
|
export const ModalsView = memo(({ modalsManager, modals, style, className, ...props }) => {
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -20,7 +20,7 @@ export const ModalsView = memo(({ modalsManager, modals, style, className }) =>
|
||||||
}, [modalsManager, ref]);
|
}, [modalsManager, ref]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={ref} style={style} className={`${styles.Container} ${className}`}>
|
<div ref={ref} style={style} className={`${styles.Container} ${className ?? ""}`} {...props}>
|
||||||
{modals?.map((modal) =>
|
{modals?.map((modal) =>
|
||||||
<ModalView key={modal.id} modal={modal}/>
|
<ModalView key={modal.id} modal={modal}/>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
.Container {
|
.Container {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 9;
|
z-index: 11;
|
||||||
}
|
}
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
import Modal from "../../../features/modals/modal.js";
|
|
||||||
import { formatShortcut } from "../../../features/utils/string.js";
|
|
||||||
import styles from "./ContextMenu.module.css";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {object} props
|
|
||||||
* @param {Modal} props.modal
|
|
||||||
* @param {Object<string, Function>} props.options
|
|
||||||
* @param {Object<string, string[]>} props.shortcuts
|
|
||||||
*/
|
|
||||||
export function ContextMenu({ modal, options, shortcuts }) {
|
|
||||||
return (<div className={styles.Container}>
|
|
||||||
{Object.entries(options).map(([label, callback]) =>
|
|
||||||
<button className={styles.Button} key={label} tabIndex={0} onClick={() => {
|
|
||||||
modal.close();
|
|
||||||
callback(modal.props?.params);
|
|
||||||
}}>
|
|
||||||
<p className={styles.Label}>{label}</p>
|
|
||||||
{shortcuts && Object.keys(shortcuts).includes(label)
|
|
||||||
? <p className={styles.Shortcut}>{formatShortcut(shortcuts[label])}</p>
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>);
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
.Container {
|
|
||||||
padding: 0.5rem;
|
|
||||||
border-top-left-radius: 0 !important;
|
|
||||||
background-color: var(--background-color-b) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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: hsla(var(--background-color-a-hsl), 75%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.Label, .Shortcut {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Shortcut {
|
|
||||||
color: var(--foreground-color-b);
|
|
||||||
}
|
|
||||||
|
|
@ -7,30 +7,18 @@ import utilStyles from "../../../styles/utils.module.css";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { faXmark } from "@fortawesome/free-solid-svg-icons";
|
import { faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { DIALOG_CONTENT_TYPES } from "../../../constants/modals.js";
|
import { DIALOG_CONTENT_TYPES } from "../../../constants/modals.js";
|
||||||
|
import { useScreenDimensions } from "../../../hooks/utils/screen.js";
|
||||||
|
|
||||||
export function DialogBox({ modal, params }) {
|
export function DialogBox({ modal, params }) {
|
||||||
const { app, title, children } = params;
|
const { app, title, children } = params;
|
||||||
|
|
||||||
const nodeRef = useRef(null);
|
const nodeRef = useRef(null);
|
||||||
|
|
||||||
const [initialised, setInitialised] = useState(false);
|
|
||||||
const [startPosition, setStartPosition] = useState(modal.position);
|
const [startPosition, setStartPosition] = useState(modal.position);
|
||||||
|
const [screenWidth, screenHeight] = useScreenDimensions();
|
||||||
const [screenWidth, setScreenWidth] = useState(100);
|
|
||||||
const [screenHeight, setScreenHeight] = useState(100);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const resizeObserver = new ResizeObserver((event) => {
|
if (screenWidth == null || screenHeight == null)
|
||||||
setScreenWidth(event[0].contentBoxSize[0].inlineSize);
|
|
||||||
setScreenHeight(event[0].contentBoxSize[0].blockSize);
|
|
||||||
setInitialised(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
resizeObserver.observe(document.getElementById("root"));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!initialised)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (modal.size.x > screenWidth || modal.size.y > screenHeight) {
|
if (modal.size.x > screenWidth || modal.size.y > screenHeight) {
|
||||||
|
|
@ -45,7 +33,7 @@ export function DialogBox({ modal, params }) {
|
||||||
setStartPosition(modal.position);
|
setStartPosition(modal.position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [initialised, modal, screenHeight, screenWidth]);
|
}, [modal, screenHeight, screenWidth]);
|
||||||
|
|
||||||
const onClick = (event) => {
|
const onClick = (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { memo, useRef, useState } from "react";
|
import { memo, useRef, useState } from "react";
|
||||||
import styles from "./TaskBar.module.css";
|
import styles from "./TaskBar.module.css";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { faSearch } from "@fortawesome/free-solid-svg-icons";
|
import { faCog, faSearch } from "@fortawesome/free-solid-svg-icons";
|
||||||
import AppsManager from "../../features/applications/applications.js";
|
import AppsManager from "../../features/applications/applications.js";
|
||||||
import { ReactSVG } from "react-svg";
|
import { ReactSVG } from "react-svg";
|
||||||
import { HomeMenu } from "./menus/HomeMenu.jsx";
|
import { HomeMenu } from "./menus/HomeMenu.jsx";
|
||||||
|
|
@ -13,24 +13,36 @@ import { SearchMenu } from "./menus/SearchMenu.jsx";
|
||||||
import { Calendar } from "./indicators/Calendar.jsx";
|
import { Calendar } from "./indicators/Calendar.jsx";
|
||||||
import { useScrollWithShadow } from "../../hooks/utils/scrollWithShadows.js";
|
import { useScrollWithShadow } from "../../hooks/utils/scrollWithShadows.js";
|
||||||
import { AppButton } from "./AppButton.jsx";
|
import { AppButton } from "./AppButton.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 "../../constants/applications.js";
|
||||||
|
import { useWindowsManager } from "../../hooks/windows/windowsManagerContext.js";
|
||||||
|
import { ModalsView } from "../modals/ModalsView.jsx";
|
||||||
|
import { TASK_BAR_HEIGHT } from "../../constants/taskBar.js";
|
||||||
|
|
||||||
export const Taskbar = memo(() => {
|
export const Taskbar = memo(() => {
|
||||||
const [showHome, setShowHome] = useState(false);
|
const [showHome, setShowHome] = useState(false);
|
||||||
const [showSearch, setShowSearch] = useState(false);
|
const [showSearch, setShowSearch] = useState(false);
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
const { boxShadow, onUpdate } = useScrollWithShadow({
|
const { boxShadow, onUpdate } = useScrollWithShadow({ ref, shadow: {
|
||||||
ref,
|
offset: 20,
|
||||||
shadow: {
|
blurRadius: 10,
|
||||||
offset: 20,
|
spreadRadius: -10,
|
||||||
blurRadius: 10,
|
color: { a: 25 }
|
||||||
spreadRadius: -10,
|
} });
|
||||||
color: {
|
|
||||||
a: 25
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const inputRef = useRef(null);
|
const inputRef = useRef(null);
|
||||||
|
const [modalsManager, modals] = useModals();
|
||||||
|
const windowsManager = useWindowsManager();
|
||||||
|
const { onContextMenu } = useContextMenu({ modalsManager, Actions: (props) =>
|
||||||
|
<Actions avoidTaskBar={false} {...props}>
|
||||||
|
<ClickAction label={`Open ${APP_NAMES.SETTINGS}`} icon={faCog} onTrigger={() => {
|
||||||
|
windowsManager.open(APPS.SETTINGS);
|
||||||
|
}}/>
|
||||||
|
</Actions>
|
||||||
|
});
|
||||||
|
|
||||||
const updateShowHome = (value) => {
|
const updateShowHome = (value) => {
|
||||||
setShowHome(value);
|
setShowHome(value);
|
||||||
|
|
@ -67,8 +79,17 @@ export const Taskbar = memo(() => {
|
||||||
updateShowSearch(true);
|
updateShowSearch(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (<>
|
||||||
<div className={styles["Task-bar"]}>
|
<ModalsView modalsManager={modalsManager} modals={modals}/>
|
||||||
|
<div
|
||||||
|
style={{ "--task-bar-height": `${TASK_BAR_HEIGHT}px` }}
|
||||||
|
className={styles["Task-bar"]}
|
||||||
|
data-allow-context-menu={true}
|
||||||
|
onContextMenu={(event) => {
|
||||||
|
if (event.target.getAttribute("data-allow-context-menu"))
|
||||||
|
onContextMenu(event);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div className={styles["Menu-icons"]}>
|
<div className={styles["Menu-icons"]}>
|
||||||
<div className={styles["Home-container"]}>
|
<div className={styles["Home-container"]}>
|
||||||
<OutsideClickListener onOutsideClick={() => { updateShowHome(false); }}>
|
<OutsideClickListener onOutsideClick={() => { updateShowHome(false); }}>
|
||||||
|
|
@ -103,8 +124,14 @@ export const Taskbar = memo(() => {
|
||||||
</OutsideClickListener>
|
</OutsideClickListener>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles["App-icons"]} style={{ boxShadow }}>
|
<div className={styles["App-icons"]} data-allow-context-menu={true} style={{ boxShadow }}>
|
||||||
<div className={styles["App-icons-inner"]} onScroll={onUpdate} onResize={onUpdate} ref={ref}>
|
<div
|
||||||
|
className={styles["App-icons-inner"]}
|
||||||
|
data-allow-context-menu={true}
|
||||||
|
onScroll={onUpdate}
|
||||||
|
onResize={onUpdate}
|
||||||
|
ref={ref}
|
||||||
|
>
|
||||||
{AppsManager.APPLICATIONS.map((app) =>
|
{AppsManager.APPLICATIONS.map((app) =>
|
||||||
<AppButton app={app} key={app.id}/>
|
<AppButton app={app} key={app.id}/>
|
||||||
)}
|
)}
|
||||||
|
|
@ -118,5 +145,5 @@ export const Taskbar = memo(() => {
|
||||||
<button title="View Desktop" id="desktop-button"/>
|
<button title="View Desktop" id="desktop-button"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
</>);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
@ -1,18 +1,39 @@
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import styles from "./Button.module.css";
|
import styles from "./Button.module.css";
|
||||||
|
import { faExternalLink } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} props
|
* @param {object} props
|
||||||
|
* @param {string} props.className
|
||||||
|
* @param {string} props.href
|
||||||
*/
|
*/
|
||||||
export function Button(props) {
|
export function Button(props) {
|
||||||
const { className = "", children } = props;
|
let { className = "" } = props;
|
||||||
|
const { href, children } = props;
|
||||||
|
|
||||||
return (
|
className = `${styles.Button} ${className}`;
|
||||||
<button
|
|
||||||
className={`${className} ${styles.Button}`}
|
if (href != null) {
|
||||||
|
className = `${styles["Button-link"]} ${className}`;
|
||||||
|
|
||||||
|
return (<a
|
||||||
|
href={href}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
{...props}
|
{...props}
|
||||||
|
className={className}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</button>
|
<FontAwesomeIcon icon={faExternalLink}/>
|
||||||
);
|
</a>);
|
||||||
|
} else {
|
||||||
|
return (<button
|
||||||
|
tabIndex={0}
|
||||||
|
{...props}
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,8 +1,25 @@
|
||||||
.Button {
|
.Button {
|
||||||
color: var(--foreground-color-a);
|
--text-color: var(--foreground-color-a);
|
||||||
background: none;
|
--normal-color: var(--background-color-a);
|
||||||
|
--hover-color: var(--background-color-b);
|
||||||
|
|
||||||
|
color: var(--text-color);
|
||||||
|
background-color: var(--normal-color);
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
font-size: 0.875em;
|
||||||
transition: background-color 100ms ease-in-out;
|
transition: background-color 100ms ease-in-out;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Button:hover, .Button:focus-visible {
|
||||||
|
background-color: var(--hover-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Button-link {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Button-link > svg {
|
||||||
|
margin-left: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import styles from "./WindowView.module.css";
|
import styles from "./WindowView.module.css";
|
||||||
import { faMinus, faWindowMaximize as fasWindowMaximize, faXmark } from "@fortawesome/free-solid-svg-icons";
|
import { faExpand, faMinus, faWindowMaximize as fasWindowMaximize, faTimes, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { ReactSVG } from "react-svg";
|
import { ReactSVG } from "react-svg";
|
||||||
import { useWindowsManager } from "../../hooks/windows/windowsManagerContext.js";
|
import { useWindowsManager } from "../../hooks/windows/windowsManagerContext.js";
|
||||||
import Draggable from "react-draggable";
|
import Draggable from "react-draggable";
|
||||||
|
|
@ -13,6 +13,9 @@ import { useModals } from "../../hooks/modals/modals.js";
|
||||||
import { ModalsView } from "../modals/ModalsView.jsx";
|
import { ModalsView } from "../modals/ModalsView.jsx";
|
||||||
import { useContextMenu } from "../../hooks/modals/contextMenu.js";
|
import { useContextMenu } from "../../hooks/modals/contextMenu.js";
|
||||||
import AppsManager from "../../features/applications/applications.js";
|
import AppsManager from "../../features/applications/applications.js";
|
||||||
|
import { ClickAction } from "../actions/actions/ClickAction.jsx";
|
||||||
|
import { Actions } from "../actions/Actions.jsx";
|
||||||
|
import { useScreenDimensions } from "../../hooks/utils/screen.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {object} windowProps
|
* @typedef {object} windowProps
|
||||||
|
|
@ -41,43 +44,30 @@ export const WindowView = memo(({ id, app, size, position, onInteract, options,
|
||||||
const nodeRef = useRef(null);
|
const nodeRef = useRef(null);
|
||||||
const [modalsManager, modals] = useModals();
|
const [modalsManager, modals] = useModals();
|
||||||
|
|
||||||
const [initialised, setInitialised] = useState(false);
|
|
||||||
const [startSize, setStartSize] = useState(size);
|
const [startSize, setStartSize] = useState(size);
|
||||||
const [startPosition, setStartPosition] = useState(position);
|
const [startPosition, setStartPosition] = useState(position);
|
||||||
|
|
||||||
const [maximized, setMaximized] = useState(false);
|
const [maximized, setMaximized] = useState(false);
|
||||||
const [minimized, setMinimized] = useState(false);
|
const [minimized, setMinimized] = useState(false);
|
||||||
|
|
||||||
const [screenWidth, setScreenWidth] = useState(100);
|
const [screenWidth, screenHeight] = useScreenDimensions();
|
||||||
const [screenHeight, setScreenHeight] = useState(100);
|
|
||||||
|
|
||||||
const [title, setTitle] = useState(app.name);
|
const [title, setTitle] = useState(app.name);
|
||||||
const [iconUrl, setIconUrl] = useState(AppsManager.getAppIconUrl(app.id));
|
const [iconUrl, setIconUrl] = useState(AppsManager.getAppIconUrl(app.id));
|
||||||
|
|
||||||
const { onContextMenu } = useContextMenu({
|
const { onContextMenu, ShortcutsListener } = useContextMenu({ modalsManager, Actions: (props) =>
|
||||||
modalsManager,
|
<Actions {...props}>
|
||||||
options: {
|
<ClickAction label="Maximize" icon={faExpand} shortcut={["F11"]} onTrigger={() => {
|
||||||
"Maximize": () => { setMaximized(!maximized); },
|
setMaximized(!maximized);
|
||||||
"Close": () => { close(); }
|
}}/>
|
||||||
},
|
<ClickAction label="Close" icon={faTimes} shortcut={["Control", "q"]} onTrigger={() => {
|
||||||
shortcuts: {
|
close();
|
||||||
"Maximize": ["F11"],
|
}}/>
|
||||||
"Close": ["Control", "q"]
|
</Actions>
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const resizeObserver = new ResizeObserver((event) => {
|
if (screenWidth == null || screenHeight == null)
|
||||||
setScreenWidth(event[0].contentBoxSize[0].inlineSize);
|
|
||||||
setScreenHeight(event[0].contentBoxSize[0].blockSize);
|
|
||||||
setInitialised(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
resizeObserver.observe(document.getElementById("root"));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!initialised)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (size.x > screenWidth || size.y > screenHeight) {
|
if (size.x > screenWidth || size.y > screenHeight) {
|
||||||
|
|
@ -94,7 +84,7 @@ export const WindowView = memo(({ id, app, size, position, onInteract, options,
|
||||||
setStartPosition(position);
|
setStartPosition(position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [initialised, position, size, screenHeight, screenWidth]);
|
}, [position, size, screenHeight, screenWidth]);
|
||||||
|
|
||||||
const close = (event) => {
|
const close = (event) => {
|
||||||
event?.preventDefault();
|
event?.preventDefault();
|
||||||
|
|
@ -119,6 +109,7 @@ export const WindowView = memo(({ id, app, size, position, onInteract, options,
|
||||||
classNames.push(styles.Minimized);
|
classNames.push(styles.Minimized);
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
|
<ShortcutsListener/>
|
||||||
<ModalsView modalsManager={modalsManager} modals={modals} style={{ zIndex: 1 }}/>
|
<ModalsView modalsManager={modalsManager} modals={modals} style={{ zIndex: 1 }}/>
|
||||||
<Draggable
|
<Draggable
|
||||||
key={id}
|
key={id}
|
||||||
|
|
|
||||||
1
src/constants/taskBar.js
Normal file
1
src/constants/taskBar.js
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export const TASK_BAR_HEIGHT = 3 * 16;
|
||||||
|
|
@ -1,2 +1 @@
|
||||||
export const SCREEN_MARGIN = 32;
|
export const SCREEN_MARGIN = 32;
|
||||||
export const TASKBAR_HEIGHT = 48;
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
*/
|
*/
|
||||||
export function closeViewport(requireConfirmation = false) {
|
export function closeViewport(requireConfirmation = false) {
|
||||||
if (requireConfirmation && window.confirm("Are you sure you want to shut down ProzillaOS?")) {
|
if (requireConfirmation && window.confirm("Are you sure you want to shut down ProzillaOS?")) {
|
||||||
|
console.info("Closing viewport");
|
||||||
window.open("about:blank", "_self");
|
window.open("about:blank", "_self");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -13,5 +14,6 @@ export function closeViewport(requireConfirmation = false) {
|
||||||
* @param {boolean} bypassCache
|
* @param {boolean} bypassCache
|
||||||
*/
|
*/
|
||||||
export function reloadViewport(bypassCache = false) {
|
export function reloadViewport(bypassCache = false) {
|
||||||
|
console.info("Reloading viewport");
|
||||||
window.location.reload(bypassCache);
|
window.location.reload(bypassCache);
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { SCREEN_MARGIN, TASKBAR_HEIGHT } from "../../constants/windows.js";
|
import { TASK_BAR_HEIGHT } from "../../constants/taskBar.js";
|
||||||
|
import { SCREEN_MARGIN } from "../../constants/windows.js";
|
||||||
import AppsManager from "../applications/applications.js";
|
import AppsManager from "../applications/applications.js";
|
||||||
import { randomRange } from "../math/random.js";
|
import { randomRange } from "../math/random.js";
|
||||||
import Vector2 from "../math/vector2.js";
|
import Vector2 from "../math/vector2.js";
|
||||||
|
|
@ -25,7 +26,7 @@ export default class WindowsManager {
|
||||||
|
|
||||||
const size = options?.size ?? app.windowOptions?.size ?? new Vector2(700, 400);
|
const size = options?.size ?? app.windowOptions?.size ?? new Vector2(700, 400);
|
||||||
const position = new Vector2(randomRange(SCREEN_MARGIN, window.innerWidth - size.x - SCREEN_MARGIN),
|
const position = new Vector2(randomRange(SCREEN_MARGIN, window.innerWidth - size.x - SCREEN_MARGIN),
|
||||||
randomRange(SCREEN_MARGIN, window.innerHeight - size.y - SCREEN_MARGIN - TASKBAR_HEIGHT));
|
randomRange(SCREEN_MARGIN, window.innerHeight - size.y - SCREEN_MARGIN - TASK_BAR_HEIGHT));
|
||||||
|
|
||||||
let id = 0;
|
let id = 0;
|
||||||
while (this.windowIds.includes(id.toString())) {
|
while (this.windowIds.includes(id.toString())) {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import { ContextMenu } from "../../components/modals/context-menu/ContextMenu.jsx";
|
|
||||||
import Vector2 from "../../features/math/vector2.js";
|
import Vector2 from "../../features/math/vector2.js";
|
||||||
import Modal from "../../features/modals/modal.js";
|
import Modal from "../../features/modals/modal.js";
|
||||||
import ModalsManager from "../../features/modals/modals.js";
|
import ModalsManager from "../../features/modals/modals.js";
|
||||||
import { useShortcuts } from "../utils/keyboard.js";
|
import { STYLES } from "../../components/actions/Actions.jsx";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Function} onContextMenuType
|
* @typedef {Function} onContextMenuType
|
||||||
|
|
@ -15,11 +14,13 @@ import { useShortcuts } from "../utils/keyboard.js";
|
||||||
/**
|
/**
|
||||||
* @param {object} props
|
* @param {object} props
|
||||||
* @param {ModalsManager} props.modalsManager
|
* @param {ModalsManager} props.modalsManager
|
||||||
* @param {Object<string, Object<string, Function>>} props.options
|
* @param {import("../../components/actions/Actions.jsx").actionsType} props.Actions
|
||||||
* @param {Object<string, Object<string, string[]>>} props.shortcuts
|
* @returns {{
|
||||||
* @returns {{ onContextMenu: onContextMenuType }}
|
* onContextMenu: onContextMenuType,
|
||||||
|
* ShortcutsListener: import("../../components/actions/Actions.jsx").actionsType
|
||||||
|
* }}
|
||||||
*/
|
*/
|
||||||
export function useContextMenu({ modalsManager, options, shortcuts }) {
|
export function useContextMenu({ modalsManager, Actions }) {
|
||||||
// Open a new modal when context menu is triggered
|
// Open a new modal when context menu is triggered
|
||||||
const onContextMenu = useCallback((event, params = {}) => {
|
const onContextMenu = useCallback((event, params = {}) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
@ -34,15 +35,21 @@ export function useContextMenu({ modalsManager, options, shortcuts }) {
|
||||||
positionY -= containerRect.y / 2;
|
positionY -= containerRect.y / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newModal = new Modal(ContextMenu)
|
const newModal = new Modal(Actions)
|
||||||
.setPosition(new Vector2(positionX, positionY))
|
.setPosition(new Vector2(positionX, positionY))
|
||||||
.setProps({ options, shortcuts, params });
|
.setProps({
|
||||||
|
triggerParams: params,
|
||||||
|
className: STYLES.CONTEXT_MENU,
|
||||||
|
onAnyTrigger: () => {
|
||||||
|
newModal.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
modalsManager.open(newModal);
|
modalsManager.open(newModal);
|
||||||
return newModal;
|
return newModal;
|
||||||
}, [modalsManager, options, shortcuts]);
|
}, [Actions, modalsManager]);
|
||||||
|
|
||||||
useShortcuts({ options, shortcuts, useCategories: false });
|
const ShortcutsListener = () => <><Actions className={STYLES.SHORTCUTS_LISTENER}/></>;
|
||||||
|
|
||||||
return { onContextMenu };
|
return { onContextMenu, ShortcutsListener };
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Object} params
|
* @param {object} params
|
||||||
* @param {Function} params.onMouseDown
|
* @param {Function} params.onMouseDown
|
||||||
* @param {Function} params.onMouseUp
|
* @param {Function} params.onMouseUp
|
||||||
* @param {Function} params.onClick
|
* @param {Function} params.onClick
|
||||||
|
|
|
||||||
20
src/hooks/utils/screen.js
Normal file
20
src/hooks/utils/screen.js
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {[number, number]}
|
||||||
|
*/
|
||||||
|
export function useScreenDimensions() {
|
||||||
|
const [screenWidth, setScreenWidth] = useState(null);
|
||||||
|
const [screenHeight, setScreenHeight] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const resizeObserver = new ResizeObserver((event) => {
|
||||||
|
setScreenWidth(event[0].contentBoxSize[0].inlineSize);
|
||||||
|
setScreenHeight(event[0].contentBoxSize[0].blockSize);
|
||||||
|
});
|
||||||
|
|
||||||
|
resizeObserver.observe(document.getElementById("root"));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return [screenWidth, screenHeight];
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue