Updated desktop context menu
This commit is contained in:
parent
ed80c17dfa
commit
f51d938a09
18 changed files with 249 additions and 54 deletions
|
|
@ -1,3 +1,4 @@
|
||||||
<options>
|
<options>
|
||||||
<wallpaper>/assets/wallpapers/vibrant-wallpaper-purple-yellow.png</wallpaper>
|
<wallpaper>/assets/wallpapers/vibrant-wallpaper-purple-yellow.png</wallpaper>
|
||||||
|
<show-icons>true</show-icons>
|
||||||
</options>
|
</options>
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
import { Children, cloneElement, isValidElement, useEffect, useRef, useState } from "react";
|
import { Children, cloneElement, isValidElement } from "react";
|
||||||
import { useShortcuts } from "../../hooks/utils/keyboard.js";
|
import { useShortcuts } from "../../hooks/utils/keyboard.js";
|
||||||
import styles from "./Actions.module.css";
|
import styles from "./Actions.module.css";
|
||||||
import { useScreenDimensions } from "../../hooks/utils/screen.js";
|
import { useScreenBounds } from "../../hooks/utils/screen.js";
|
||||||
import { TASKBAR_HEIGHT } from "../../constants/taskBar.js";
|
|
||||||
|
|
||||||
export const STYLES = {
|
export const STYLES = {
|
||||||
CONTEXT_MENU: styles["Context-menu"],
|
CONTEXT_MENU: styles["Context-menu"],
|
||||||
|
|
@ -34,11 +33,7 @@ export const STYLES = {
|
||||||
export function Actions({ children, className, onAnyTrigger, triggerParams, avoidTaskbar = true }) {
|
export function Actions({ children, className, onAnyTrigger, triggerParams, avoidTaskbar = true }) {
|
||||||
const isListener = (className === STYLES.SHORTCUTS_LISTENER);
|
const isListener = (className === STYLES.SHORTCUTS_LISTENER);
|
||||||
|
|
||||||
const ref = useRef(null);
|
const { ref, initiated, alignLeft, alignTop } = useScreenBounds({ avoidTaskbar });
|
||||||
const [initiated, setInitiated] = useState(false);
|
|
||||||
const [alignLeft, setAlignLeft] = useState(false);
|
|
||||||
const [alignTop, setAlignTop] = useState(false);
|
|
||||||
const [screenWidth, screenHeight] = useScreenDimensions();
|
|
||||||
|
|
||||||
const options = {};
|
const options = {};
|
||||||
const shortcuts = {};
|
const shortcuts = {};
|
||||||
|
|
@ -68,9 +63,9 @@ export function Actions({ children, className, onAnyTrigger, triggerParams, avoi
|
||||||
...child.props,
|
...child.props,
|
||||||
actionId,
|
actionId,
|
||||||
children: iterateOverChildren(child.props.children),
|
children: iterateOverChildren(child.props.children),
|
||||||
onTrigger: (event) => {
|
onTrigger: (event, ...args) => {
|
||||||
onAnyTrigger?.(event, triggerParams);
|
onAnyTrigger?.(event, triggerParams, ...args);
|
||||||
onTrigger?.(event, triggerParams);
|
onTrigger?.(event, triggerParams, ...args);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -80,28 +75,6 @@ export function Actions({ children, className, onAnyTrigger, triggerParams, avoi
|
||||||
|
|
||||||
useShortcuts({ options, shortcuts, useCategories: false });
|
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 -= TASKBAR_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)
|
if (isListener)
|
||||||
return iterateOverChildren(children);
|
return iterateOverChildren(children);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,9 @@
|
||||||
|
|
||||||
.Context-menu.Container {
|
.Context-menu.Container {
|
||||||
--border-radius: 0.5rem;
|
--border-radius: 0.5rem;
|
||||||
|
--padding: 0.375rem;
|
||||||
|
|
||||||
padding: 0.375rem;
|
padding: var(--padding);
|
||||||
border-top-left-radius: calc((1 - var(--right) * var(--bottom)) * var(--border-radius)) !important;
|
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-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-left-radius: calc((1 - var(--right) * var(--top)) * var(--border-radius)) !important;
|
||||||
|
|
@ -41,7 +42,8 @@
|
||||||
background-color: var(--background-color-b) !important;
|
background-color: var(--background-color-b) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Context-menu .Button {
|
.Context-menu .Button,
|
||||||
|
.Context-menu .Dropdown {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
@ -58,7 +60,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.Context-menu .Button:hover,
|
.Context-menu .Button:hover,
|
||||||
.Context-menu .Button:focus-visible {
|
.Context-menu .Button:focus-visible,
|
||||||
|
.Context-menu .Dropdown:hover,
|
||||||
|
.Context-menu .Dropdown:focus-visible {
|
||||||
background-color: hsla(var(--background-color-a-hsl), 75%);
|
background-color: hsla(var(--background-color-a-hsl), 75%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,4 +92,34 @@
|
||||||
|
|
||||||
.Context-menu .Shortcut {
|
.Context-menu .Shortcut {
|
||||||
color: var(--foreground-color-b);
|
color: var(--foreground-color-b);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Context-menu .Dropdown {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Context-menu .Dropdown .Dropdown-content {
|
||||||
|
opacity: 1;
|
||||||
|
position: absolute;
|
||||||
|
top: calc(var(--padding) * -1);
|
||||||
|
left: 100%;
|
||||||
|
padding: var(--padding);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
background-color: var(--background-color-b);
|
||||||
|
transition: opacity 100ms ease-out;
|
||||||
|
cursor: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Context-menu .Dropdown:not(.Active) .Dropdown-content {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Context-menu .Divider {
|
||||||
|
width: calc(100% - 0.5rem);
|
||||||
|
height: 0.1rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
background-color: var(--foreground-color-c);
|
||||||
|
margin: 0.5rem auto;
|
||||||
}
|
}
|
||||||
5
src/components/actions/actions/Divider.jsx
Normal file
5
src/components/actions/actions/Divider.jsx
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import styles from "../Actions.module.css";
|
||||||
|
|
||||||
|
export function Divider() {
|
||||||
|
return <div className={styles.Divider}/>;
|
||||||
|
}
|
||||||
29
src/components/actions/actions/DropdownAction.jsx
Normal file
29
src/components/actions/actions/DropdownAction.jsx
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import styles from "../Actions.module.css";
|
||||||
|
import { faCaretRight } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
export function DropdownAction({ label, icon, children }) {
|
||||||
|
const [showContent, setShowContent] = useState(false);
|
||||||
|
|
||||||
|
const classNames = [styles.Dropdown];
|
||||||
|
if (showContent)
|
||||||
|
classNames.push(styles.Active);
|
||||||
|
|
||||||
|
return (<div
|
||||||
|
key={label}
|
||||||
|
className={classNames.join(" ")}
|
||||||
|
tabIndex={0}
|
||||||
|
onMouseEnter={() => { setShowContent(true); }}
|
||||||
|
onMouseLeave={() => { setShowContent(false); }}
|
||||||
|
>
|
||||||
|
<span className={styles.Label}>
|
||||||
|
{icon && <div className={styles.Icon}><FontAwesomeIcon icon={icon}/></div>}
|
||||||
|
<p>{label}</p>
|
||||||
|
</span>
|
||||||
|
<div className={styles["Dropdown-arrow"]}><FontAwesomeIcon icon={faCaretRight}/></div>
|
||||||
|
<div className={styles["Dropdown-content"]}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
40
src/components/actions/actions/RadioAction.jsx
Normal file
40
src/components/actions/actions/RadioAction.jsx
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { formatShortcut } from "../../../features/utils/string.js";
|
||||||
|
import styles from "../Actions.module.css";
|
||||||
|
import { faCircleDot } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { faCircle } from "@fortawesome/free-regular-svg-icons";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} props
|
||||||
|
* @param {string} props.actionId
|
||||||
|
* @param {{
|
||||||
|
* label: string,
|
||||||
|
* shortcut: string[]
|
||||||
|
* }[]} props.options
|
||||||
|
* @param {number} props.initialIndex
|
||||||
|
* @param {Function} props.onTrigger
|
||||||
|
*/
|
||||||
|
export function RadioAction({ actionId, options, initialIndex, onTrigger }) {
|
||||||
|
const [activeIndex, setActiveIndex] = useState(initialIndex ?? 0);
|
||||||
|
|
||||||
|
return (<div key={actionId}>
|
||||||
|
{options.map(({ label, shortcut }, index) =>
|
||||||
|
<button key={label} className={styles.Button} tabIndex={0} onClick={(event) => {
|
||||||
|
setActiveIndex(index);
|
||||||
|
onTrigger(event, index);
|
||||||
|
}}>
|
||||||
|
<span className={styles.Label}>
|
||||||
|
<div className={styles.Icon}>
|
||||||
|
{activeIndex === index
|
||||||
|
? <FontAwesomeIcon icon={faCircleDot}/>
|
||||||
|
: <FontAwesomeIcon icon={faCircle}/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<p>{label}</p>
|
||||||
|
</span>
|
||||||
|
{shortcut && <p className={styles.Shortcut}>{formatShortcut(shortcut)}</p>}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
26
src/components/actions/actions/ToggleAction.jsx
Normal file
26
src/components/actions/actions/ToggleAction.jsx
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { formatShortcut } from "../../../features/utils/string.js";
|
||||||
|
import styles from "../Actions.module.css";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { faSquare } from "@fortawesome/free-regular-svg-icons";
|
||||||
|
import { faSquareCheck } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
|
export function ToggleAction({ actionId, label, shortcut, initialValue, onTrigger }) {
|
||||||
|
const [active, setActive] = useState(initialValue ?? false);
|
||||||
|
|
||||||
|
return (<button key={actionId} className={styles.Button} tabIndex={0} onClick={(event) => {
|
||||||
|
onTrigger(event, !active);
|
||||||
|
setActive(!active);
|
||||||
|
}}>
|
||||||
|
<span className={styles.Label}>
|
||||||
|
<div className={styles.Icon}>
|
||||||
|
{active
|
||||||
|
? <FontAwesomeIcon icon={faSquareCheck}/>
|
||||||
|
: <FontAwesomeIcon icon={faSquare}/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<p>{label}</p>
|
||||||
|
</span>
|
||||||
|
{shortcut && <p className={styles.Shortcut}>{formatShortcut(shortcut)}</p>}
|
||||||
|
</button>);
|
||||||
|
}
|
||||||
|
|
@ -217,7 +217,10 @@ export function FileExplorer({ startPath, app, modalsManager }) {
|
||||||
showHidden={showHidden}
|
showHidden={showHidden}
|
||||||
onClickFile={(event, file) => {
|
onClickFile={(event, file) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
windowsManager.openFile(file, { mode: "view" });
|
const options = {};
|
||||||
|
if (file.extension === "md")
|
||||||
|
options.mode = "view";
|
||||||
|
windowsManager.openFile(file, options);
|
||||||
}}
|
}}
|
||||||
onClickFolder={(event, folder) => {
|
onClickFolder={(event, folder) => {
|
||||||
changeDirectory(folder.linkedPath ?? folder.name);
|
changeDirectory(folder.linkedPath ?? folder.name);
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.Main {
|
.Main {
|
||||||
|
--scale: inherit !important;
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
||||||
|
|
@ -63,15 +63,11 @@ export function DirectoryList({ directory, showHidden = false, folderClassName,
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener("mousemove", onMoveRectSelect);
|
document.addEventListener("mousemove", onMoveRectSelect);
|
||||||
document.addEventListener("pointermove", onMoveRectSelect);
|
|
||||||
document.addEventListener("mouseup", onStopRectSelect);
|
document.addEventListener("mouseup", onStopRectSelect);
|
||||||
document.addEventListener("pointerup", onStopRectSelect);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener("mousemove", onMoveRectSelect);
|
document.removeEventListener("mousemove", onMoveRectSelect);
|
||||||
document.removeEventListener("pointermove", onMoveRectSelect);
|
|
||||||
document.removeEventListener("mouseup", onStopRectSelect);
|
document.removeEventListener("mouseup", onStopRectSelect);
|
||||||
document.removeEventListener("pointerup", onStopRectSelect);
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -140,7 +136,6 @@ export function DirectoryList({ directory, showHidden = false, folderClassName,
|
||||||
className={classNames.join(" ")}
|
className={classNames.join(" ")}
|
||||||
onClick={clearSelection}
|
onClick={clearSelection}
|
||||||
onMouseDown={onStartRectSelect}
|
onMouseDown={onStartRectSelect}
|
||||||
onPointerDown={onStartRectSelect}
|
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{rectSelectStart != null && rectSelectEnd != null
|
{rectSelectStart != null && rectSelectEnd != null
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
.Container {
|
.Container {
|
||||||
|
--scale: 1rem;
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { useModals } from "../../hooks/modals/modals.js";
|
||||||
import { ModalsView } from "../modals/ModalsView.jsx";
|
import { ModalsView } from "../modals/ModalsView.jsx";
|
||||||
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 { FALLBACK_WALLPAPER } from "../../constants/desktop.js";
|
import { FALLBACK_ICON_SIZE, FALLBACK_WALLPAPER } from "../../constants/desktop.js";
|
||||||
import { reloadViewport } from "../../features/utils/browser.js";
|
import { reloadViewport } from "../../features/utils/browser.js";
|
||||||
import { useVirtualRoot } from "../../hooks/virtual-drive/virtualRootContext.js";
|
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";
|
||||||
|
|
@ -15,7 +15,12 @@ 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 { Actions } from "../actions/Actions.jsx";
|
||||||
import { ClickAction } from "../actions/actions/ClickAction.jsx";
|
import { ClickAction } from "../actions/actions/ClickAction.jsx";
|
||||||
import { faArrowsRotate, faFolder, faPaintBrush, faTrash } from "@fortawesome/free-solid-svg-icons";
|
import { faArrowsRotate, faEye, faFolder, faPaintBrush, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { ToggleAction } from "../actions/actions/ToggleAction.jsx";
|
||||||
|
import { DropdownAction } from "../actions/actions/DropdownAction.jsx";
|
||||||
|
import { RadioAction } from "../actions/actions/RadioAction.jsx";
|
||||||
|
import { Divider } from "../actions/actions/Divider.jsx";
|
||||||
|
import { isValidInteger } from "../../features/utils/number.js";
|
||||||
|
|
||||||
export const Desktop = memo(() => {
|
export const Desktop = memo(() => {
|
||||||
const settingsManager = useSettingsManager();
|
const settingsManager = useSettingsManager();
|
||||||
|
|
@ -23,9 +28,26 @@ export const Desktop = memo(() => {
|
||||||
const [modalsManager, modals] = useModals();
|
const [modalsManager, modals] = useModals();
|
||||||
const windowsManager = useWindowsManager();
|
const windowsManager = useWindowsManager();
|
||||||
const virtualRoot = useVirtualRoot();
|
const virtualRoot = useVirtualRoot();
|
||||||
|
const [showIcons, setShowIcons] = useState(false);
|
||||||
|
const [iconSize, setIconSize] = useState(FALLBACK_ICON_SIZE);
|
||||||
|
|
||||||
const { onContextMenu, ShortcutsListener } = useContextMenu({ modalsManager, Actions: (props) =>
|
const { onContextMenu, ShortcutsListener } = useContextMenu({ modalsManager, Actions: (props) =>
|
||||||
<Actions {...props}>
|
<Actions {...props}>
|
||||||
|
<DropdownAction label="View" icon={faEye}>
|
||||||
|
<RadioAction initialIndex={iconSize} onTrigger={(event, params, value) => {
|
||||||
|
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop);
|
||||||
|
settings.set("icon-size", parseInt(value));
|
||||||
|
}} options={[
|
||||||
|
{ label: "Small icons" },
|
||||||
|
{ label: "Medium icons" },
|
||||||
|
{ label: "Large icons" }
|
||||||
|
]}/>
|
||||||
|
<Divider/>
|
||||||
|
<ToggleAction label="Show dekstop icons" initialValue={showIcons} onTrigger={(event, params, value) => {
|
||||||
|
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop);
|
||||||
|
settings.set("show-icons", (!showIcons).toString());
|
||||||
|
}}/>
|
||||||
|
</DropdownAction>
|
||||||
<ClickAction label="Reload" shortcut={["Control", "r"]} icon={faArrowsRotate} onTrigger={() => {
|
<ClickAction label="Reload" shortcut={["Control", "r"]} icon={faArrowsRotate} onTrigger={() => {
|
||||||
reloadViewport();
|
reloadViewport();
|
||||||
}}/>
|
}}/>
|
||||||
|
|
@ -68,6 +90,11 @@ export const Desktop = memo(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop);
|
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop);
|
||||||
settings.get("wallpaper", setWallpaper);
|
settings.get("wallpaper", setWallpaper);
|
||||||
|
settings.get("show-icons", (value) => { setShowIcons(value === "true"); });
|
||||||
|
settings.get("icon-size", (value) => {
|
||||||
|
if (isValidInteger(value))
|
||||||
|
setIconSize(parseInt(value));
|
||||||
|
});
|
||||||
})();
|
})();
|
||||||
}, [settingsManager]);
|
}, [settingsManager]);
|
||||||
|
|
||||||
|
|
@ -78,6 +105,8 @@ export const Desktop = memo(() => {
|
||||||
settings.set("wallpaper", FALLBACK_WALLPAPER);
|
settings.set("wallpaper", FALLBACK_WALLPAPER);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const iconScale = 1 + ((isValidInteger(iconSize) ? iconSize : FALLBACK_ICON_SIZE) - 1) / 4;
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
<ShortcutsListener/>
|
<ShortcutsListener/>
|
||||||
<div
|
<div
|
||||||
|
|
@ -85,18 +114,22 @@ export const Desktop = memo(() => {
|
||||||
onContextMenu={onContextMenu}
|
onContextMenu={onContextMenu}
|
||||||
>
|
>
|
||||||
<ModalsView modalsManager={modalsManager} modals={modals}/>
|
<ModalsView modalsManager={modalsManager} modals={modals}/>
|
||||||
<DirectoryList
|
{showIcons && <DirectoryList
|
||||||
directory={directory}
|
directory={directory}
|
||||||
className={styles.Content}
|
className={styles.Content}
|
||||||
|
style={{
|
||||||
|
"--scale": `${iconScale}rem`
|
||||||
|
}}
|
||||||
fileClassName={styles["Item"]}
|
fileClassName={styles["Item"]}
|
||||||
folderClassName={styles["Item"]}
|
folderClassName={styles["Item"]}
|
||||||
onClickFile={(event, file) => {
|
onClickFile={(event, file) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const options = { mode: "view" };
|
const options = {};
|
||||||
if (file.name === "info.md") {
|
if (file.name === "info.md")
|
||||||
options.size = new Vector2(575, 675);
|
options.size = new Vector2(575, 675);
|
||||||
}
|
if (file.extension === "md")
|
||||||
|
options.mode = "view";
|
||||||
|
|
||||||
windowsManager.openFile(file, options);
|
windowsManager.openFile(file, options);
|
||||||
}}
|
}}
|
||||||
|
|
@ -105,7 +138,7 @@ export const Desktop = memo(() => {
|
||||||
}}
|
}}
|
||||||
onContextMenuFile={onContextMenuFile}
|
onContextMenuFile={onContextMenuFile}
|
||||||
onContextMenuFolder={onContextMenuFolder}
|
onContextMenuFolder={onContextMenuFolder}
|
||||||
/>
|
/>}
|
||||||
{wallpaper
|
{wallpaper
|
||||||
? <img src={wallpaper} className={styles.Wallpaper} alt="Desktop wallpaper" onError={onError}/>
|
? <img src={wallpaper} className={styles.Wallpaper} alt="Desktop wallpaper" onError={onError}/>
|
||||||
: null
|
: null
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.Content {
|
.Content {
|
||||||
--scale: 1rem;
|
|
||||||
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
||||||
|
|
@ -9,4 +9,5 @@ export const WALLPAPERS = [
|
||||||
"/assets/wallpapers/wave-abstract-wallpaper-teal.png",
|
"/assets/wallpapers/wave-abstract-wallpaper-teal.png",
|
||||||
];
|
];
|
||||||
|
|
||||||
export const FALLBACK_WALLPAPER = WALLPAPERS[6];
|
export const FALLBACK_WALLPAPER = WALLPAPERS[6];
|
||||||
|
export const FALLBACK_ICON_SIZE = 1;
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import { VirtualFile } from "../virtual-drive/virtualFile.js";
|
import { VirtualFile } from "../virtual-drive/virtualFile.js";
|
||||||
import { VirtualRoot } from "../virtual-drive/virtualRoot.js";
|
import { VirtualRoot } from "../virtual-drive/virtualRoot.js";
|
||||||
|
|
||||||
|
const PARENT_NODE = "options";
|
||||||
|
|
||||||
export class Settings {
|
export class Settings {
|
||||||
xmlDoc = null;
|
xmlDoc = null;
|
||||||
|
|
||||||
|
|
@ -112,8 +114,13 @@ export class Settings {
|
||||||
if (await this.isMissingXmlDoc())
|
if (await this.isMissingXmlDoc())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (this.xmlDoc.getElementsByTagName(key)?.[0])
|
if (this.xmlDoc.getElementsByTagName(key).length > 0) {
|
||||||
this.xmlDoc.getElementsByTagName(key)[0].textContent = value;
|
this.xmlDoc.getElementsByTagName(key)[0].textContent = value;
|
||||||
|
} else if (this.xmlDoc.getElementsByTagName(PARENT_NODE).length > 0) {
|
||||||
|
const newOption = this.xmlDoc.createElement(key);
|
||||||
|
newOption.textContent = value;
|
||||||
|
this.xmlDoc.getElementsByTagName(PARENT_NODE)[0].appendChild(newOption);
|
||||||
|
}
|
||||||
|
|
||||||
await this.write();
|
await this.write();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
3
src/features/utils/number.js
Normal file
3
src/features/utils/number.js
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export function isValidInteger(number) {
|
||||||
|
return (parseInt(number) || parseInt(number) === 0);
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import { TASKBAR_HEIGHT } from "../../constants/taskBar.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {[number, number]}
|
* @returns {[number, number]}
|
||||||
|
|
@ -17,4 +18,46 @@ export function useScreenDimensions() {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return [screenWidth, screenHeight];
|
return [screenWidth, screenHeight];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} props
|
||||||
|
* @param {boolean} props.avoidTaskbar
|
||||||
|
* @returns {{
|
||||||
|
* ref,
|
||||||
|
* initiated: boolean,
|
||||||
|
* alignLeft: boolean,
|
||||||
|
* alignTop: boolean
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function useScreenBounds({ avoidTaskbar = true }) {
|
||||||
|
const ref = useRef(null);
|
||||||
|
const [initiated, setInitiated] = useState(false);
|
||||||
|
const [alignLeft, setAlignLeft] = useState(false);
|
||||||
|
const [alignTop, setAlignTop] = useState(false);
|
||||||
|
const [screenWidth, screenHeight] = useScreenDimensions();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (ref.current == null || screenWidth == null || screenHeight == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const rect = ref.current.getBoundingClientRect();
|
||||||
|
const maxX = screenWidth;
|
||||||
|
let maxY = screenHeight;
|
||||||
|
|
||||||
|
if (avoidTaskbar)
|
||||||
|
maxY -= TASKBAR_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]);
|
||||||
|
|
||||||
|
return { ref, initiated, alignLeft, alignTop };
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue