Added interactable component
This commit is contained in:
parent
e19d284a91
commit
2fab7d5193
25 changed files with 386 additions and 165 deletions
|
|
@ -60,6 +60,7 @@
|
||||||
"error",
|
"error",
|
||||||
"double"
|
"double"
|
||||||
],
|
],
|
||||||
|
"object-curly-spacing": ["warn", "always"],
|
||||||
"default-case": "off",
|
"default-case": "off",
|
||||||
"arrow-parens": "error",
|
"arrow-parens": "error",
|
||||||
"space-infix-ops": "warn",
|
"space-infix-ops": "warn",
|
||||||
|
|
|
||||||
15
public/documents/links.md
Normal file
15
public/documents/links.md
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
# Links
|
||||||
|
|
||||||
|
**ProzillaOS**
|
||||||
|
|
||||||
|
- GitHub: https://github.com/Prozilla/Prozilla-OS
|
||||||
|
|
||||||
|
**Prozilla**
|
||||||
|
|
||||||
|
- Website: https://prozilla.dev/
|
||||||
|
- Ko-fi: https://ko-fi.com/prozilla
|
||||||
|
|
||||||
|
**Crumbling City**
|
||||||
|
|
||||||
|
- Website: https://daisygames.org/crumbling-city/
|
||||||
|
- Steam page: https://store.steampowered.com/app/1520290/Crumbling_City/
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useShortcuts } from "../../../hooks/utils/keyboard.js";
|
import { useShortcuts } from "../../../hooks/utils/keyboard.js";
|
||||||
import { DropdownButton } from "../../utils/DropdownButton.jsx";
|
import { DropdownButton } from "../../utils/dropdown-button/DropdownButton.jsx";
|
||||||
import styles from "./HeaderMenu.module.css";
|
import styles from "./HeaderMenu.module.css";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,20 @@ export function FileExplorer({ startPath, app, modalsManager }) {
|
||||||
|
|
||||||
const directory = virtualRoot.navigate(value);
|
const directory = virtualRoot.navigate(value);
|
||||||
|
|
||||||
|
if (directory == null) {
|
||||||
|
openWindowedModal({
|
||||||
|
title: "Error",
|
||||||
|
iconUrl: AppsManager.getAppIconUrl(APPS.FILE_EXPLORER),
|
||||||
|
size: new Vector2(300, 150),
|
||||||
|
Modal: (props) =>
|
||||||
|
<DialogBox {...props}>
|
||||||
|
<p>Invalid path: "{value}"</p>
|
||||||
|
<button data-type={DIALOG_CONTENT_TYPES.CloseButton}>Ok</button>
|
||||||
|
</DialogBox>
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setCurrentDirectory(directory);
|
setCurrentDirectory(directory);
|
||||||
setPath(directory.root ? "/" : directory.path);
|
setPath(directory.root ? "/" : directory.path);
|
||||||
}
|
}
|
||||||
|
|
@ -185,24 +199,24 @@ export function FileExplorer({ startPath, app, modalsManager }) {
|
||||||
<div className={styles.Sidebar}>
|
<div className={styles.Sidebar}>
|
||||||
<QuickAccessButton name={"Home"} onClick={() => { changeDirectory("~"); }} icon={faHouse}/>
|
<QuickAccessButton name={"Home"} onClick={() => { changeDirectory("~"); }} icon={faHouse}/>
|
||||||
<QuickAccessButton name={"Desktop"} onClick={() => { changeDirectory("~/Desktop"); }} icon={faDesktop}/>
|
<QuickAccessButton name={"Desktop"} onClick={() => { changeDirectory("~/Desktop"); }} icon={faDesktop}/>
|
||||||
<QuickAccessButton name={"Images"} onClick={() => { changeDirectory("~/Images"); }} icon={faImage}/>
|
<QuickAccessButton name={"Images"} onClick={() => { changeDirectory("~/Pictures"); }} icon={faImage}/>
|
||||||
<QuickAccessButton name={"Documents"} onClick={() => { changeDirectory("~/Documents"); }} icon={faFileLines}/>
|
<QuickAccessButton name={"Documents"} onClick={() => { changeDirectory("~/Documents"); }} icon={faFileLines}/>
|
||||||
</div>
|
</div>
|
||||||
<div id="main" className={styles.Main}>
|
<DirectoryList
|
||||||
<DirectoryList
|
directory={currentDirectory}
|
||||||
directory={currentDirectory}
|
id="main"
|
||||||
showHidden={showHidden}
|
className={styles.Main}
|
||||||
onClickFile={(event, file) => {
|
showHidden={showHidden}
|
||||||
event.preventDefault();
|
onClickFile={(event, file) => {
|
||||||
windowsManager.openFile(file);
|
event.preventDefault();
|
||||||
}}
|
windowsManager.openFile(file, { mode: "view" });
|
||||||
onClickFolder={(event, folder) => {
|
}}
|
||||||
changeDirectory(folder.linkedPath ?? folder.name);
|
onClickFolder={(event, folder) => {
|
||||||
}}
|
changeDirectory(folder.linkedPath ?? folder.name);
|
||||||
onContextMenuFile={onContextMenuFile}
|
}}
|
||||||
onContextMenuFolder={onContextMenuFolder}
|
onContextMenuFile={onContextMenuFile}
|
||||||
/>
|
onContextMenuFolder={onContextMenuFolder}
|
||||||
</div>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className={styles.Footer}>
|
<span className={styles.Footer}>
|
||||||
<p className={utilStyles["Text-light"]}>
|
<p className={utilStyles["Text-light"]}>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { VirtualFile } from "../../../../features/virtual-drive/virtualFile.js";
|
import { VirtualFile } from "../../../../features/virtual-drive/virtualFile.js";
|
||||||
import { VirtualFolder } from "../../../../features/virtual-drive/virtualFolder.js";
|
import { VirtualFolder } from "../../../../features/virtual-drive/virtualFolder.js";
|
||||||
|
import { Interactable } from "../../../utils/interactable/Interactable.jsx";
|
||||||
import styles from "./DirectoryList.module.css";
|
import styles from "./DirectoryList.module.css";
|
||||||
import { ImagePreview } from "./ImagePreview.jsx";
|
import { ImagePreview } from "./ImagePreview.jsx";
|
||||||
|
|
||||||
|
|
@ -19,36 +21,145 @@ import { ImagePreview } from "./ImagePreview.jsx";
|
||||||
* @param {object} props
|
* @param {object} props
|
||||||
* @param {VirtualFolder} props.directory
|
* @param {VirtualFolder} props.directory
|
||||||
* @param {boolean} props.showHidden
|
* @param {boolean} props.showHidden
|
||||||
* @param {string} props.folderClassname
|
* @param {string} props.folderClassName
|
||||||
* @param {string} props.fileClassname
|
* @param {string} props.fileClassName
|
||||||
|
* @param {string} props.className
|
||||||
* @param {fileEvent} props.onContextMenuFile
|
* @param {fileEvent} props.onContextMenuFile
|
||||||
* @param {folderEvent} props.onContextMenuFolder
|
* @param {folderEvent} props.onContextMenuFolder
|
||||||
* @param {fileEvent} props.onClickFile
|
* @param {fileEvent} props.onClickFile
|
||||||
* @param {folderEvent} props.onClickFolder
|
* @param {folderEvent} props.onClickFolder
|
||||||
*/
|
*/
|
||||||
export function DirectoryList({ directory, showHidden = false, folderClassname, fileClassname,
|
export function DirectoryList({ directory, showHidden = false, folderClassName, fileClassName, className,
|
||||||
onContextMenuFile, onContextMenuFolder, onClickFile, onClickFolder }) {
|
onContextMenuFile, onContextMenuFolder, onClickFile, onClickFolder, ...props }) {
|
||||||
if (!directory)
|
const [selectedFolders, setSelectedFolders] = useState([]);
|
||||||
return null;
|
const [selectedFiles, setSelectedFiles] = useState([]);
|
||||||
|
|
||||||
|
const ref = useRef(null);
|
||||||
|
const [rectSelectStart, setRectSelectStart] = useState(null);
|
||||||
|
const [rectSelectEnd, setRectSelectEnd] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
clearSelection();
|
||||||
|
}, [directory]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onMoveRectSelect = (event) => {
|
||||||
|
if (rectSelectStart == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
setRectSelectEnd({ x: event.clientX, y: event.clientY });
|
||||||
|
};
|
||||||
|
const onStopRectSelect = (event) => {
|
||||||
|
if (rectSelectStart == null || rectSelectEnd == null) {
|
||||||
|
setRectSelectStart(null);
|
||||||
|
setRectSelectEnd(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
setRectSelectStart(null);
|
||||||
|
setRectSelectEnd(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("mousemove", onMoveRectSelect);
|
||||||
|
document.addEventListener("pointermove", onMoveRectSelect);
|
||||||
|
document.addEventListener("mouseup", onStopRectSelect);
|
||||||
|
document.addEventListener("pointerup", onStopRectSelect);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("mousemove", onMoveRectSelect);
|
||||||
|
document.removeEventListener("pointermove", onMoveRectSelect);
|
||||||
|
document.removeEventListener("mouseup", onStopRectSelect);
|
||||||
|
document.removeEventListener("pointerup", onStopRectSelect);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!directory)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const clearSelection = () => {
|
||||||
|
setSelectedFolders([]);
|
||||||
|
setSelectedFiles([]);
|
||||||
|
};
|
||||||
|
const selectFolder = (folder, exclusive = false) => {
|
||||||
|
setSelectedFolders(exclusive ? [folder.id] : [...selectedFolders, folder.id]);
|
||||||
|
if (exclusive)
|
||||||
|
setSelectedFiles([]);
|
||||||
|
};
|
||||||
|
const selectFile = (file, exclusive = false) => {
|
||||||
|
setSelectedFiles(exclusive ? [file.id] : [...selectedFiles, file.id]);
|
||||||
|
if (exclusive)
|
||||||
|
setSelectedFolders([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onStartRectSelect = (event) => {
|
||||||
|
setRectSelectStart({ x: event.clientX, y: event.clientY });
|
||||||
|
};
|
||||||
|
const getRectSelectStyle = () => {
|
||||||
|
let x, y, width, height = null;
|
||||||
|
const containerRect = ref.current?.getBoundingClientRect();
|
||||||
|
|
||||||
|
if (rectSelectStart.x < rectSelectEnd.x) {
|
||||||
|
x = rectSelectStart.x;
|
||||||
|
width = rectSelectEnd.x - rectSelectStart.x;
|
||||||
|
} else {
|
||||||
|
x = rectSelectEnd.x;
|
||||||
|
width = rectSelectStart.x - rectSelectEnd.x;
|
||||||
|
}
|
||||||
|
if (rectSelectStart.y < rectSelectEnd.y) {
|
||||||
|
y = rectSelectStart.y;
|
||||||
|
height = rectSelectEnd.y - rectSelectStart.y;
|
||||||
|
} else {
|
||||||
|
y = rectSelectEnd.y;
|
||||||
|
height = rectSelectStart.y - rectSelectEnd.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (containerRect) {
|
||||||
|
x -= containerRect.x;
|
||||||
|
y -= containerRect.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return { top: y, left: x, width, height };
|
||||||
|
};
|
||||||
|
|
||||||
|
const classNames = [styles.Container];
|
||||||
const folderClassNames = [styles["Folder-button"]];
|
const folderClassNames = [styles["Folder-button"]];
|
||||||
const fileClassNames = [styles["File-button"]];
|
const fileClassNames = [styles["File-button"]];
|
||||||
|
|
||||||
if (folderClassname)
|
if (className)
|
||||||
folderClassNames.push(folderClassname);
|
classNames.push(className);
|
||||||
if (fileClassname)
|
if (folderClassName)
|
||||||
fileClassNames.push(fileClassname);
|
folderClassNames.push(folderClassName);
|
||||||
|
if (fileClassName)
|
||||||
|
fileClassNames.push(fileClassName);
|
||||||
|
|
||||||
return <>
|
return <div
|
||||||
|
ref={ref}
|
||||||
|
className={classNames.join(" ")}
|
||||||
|
onClick={clearSelection}
|
||||||
|
onMouseDown={onStartRectSelect}
|
||||||
|
onPointerDown={onStartRectSelect}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{rectSelectStart != null && rectSelectEnd != null
|
||||||
|
? <div className={styles["Selection-rect"]} style={getRectSelectStyle()}/>
|
||||||
|
: null
|
||||||
|
}
|
||||||
{directory?.getSubFolders(showHidden)?.map((folder) =>
|
{directory?.getSubFolders(showHidden)?.map((folder) =>
|
||||||
<button
|
<Interactable
|
||||||
key={folder.id}
|
key={folder.id}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
className={folderClassNames.join(" ")}
|
className={folderClassNames.join(" ")}
|
||||||
|
data-selected={selectedFolders.includes(folder.id)}
|
||||||
onContextMenu={(event) => {
|
onContextMenu={(event) => {
|
||||||
onContextMenuFolder?.(event, folder);
|
onContextMenuFolder?.(event, folder);
|
||||||
}}
|
}}
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
|
selectFolder(folder, !event.ctrlKey);
|
||||||
|
}}
|
||||||
|
onDoubleClick={(event) => {
|
||||||
onClickFolder?.(event, folder);
|
onClickFolder?.(event, folder);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -56,17 +167,21 @@ export function DirectoryList({ directory, showHidden = false, folderClassname,
|
||||||
<ImagePreview source={folder.getIconUrl()}/>
|
<ImagePreview source={folder.getIconUrl()}/>
|
||||||
</div>
|
</div>
|
||||||
<p>{folder.name}</p>
|
<p>{folder.name}</p>
|
||||||
</button>
|
</Interactable>
|
||||||
)}
|
)}
|
||||||
{directory?.getFiles(showHidden)?.map((file) =>
|
{directory?.getFiles(showHidden)?.map((file) =>
|
||||||
<button
|
<Interactable
|
||||||
key={file.id}
|
key={file.id}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
className={fileClassNames.join(" ")}
|
className={fileClassNames.join(" ")}
|
||||||
|
data-selected={selectedFiles.includes(file.id)}
|
||||||
onContextMenu={(event) => {
|
onContextMenu={(event) => {
|
||||||
onContextMenuFile?.(event, file);
|
onContextMenuFile?.(event, file);
|
||||||
}}
|
}}
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
|
selectFile(file, !event.ctrlKey);
|
||||||
|
}}
|
||||||
|
onDoubleClick={(event) => {
|
||||||
onClickFile?.(event, file);
|
onClickFile?.(event, file);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -74,7 +189,7 @@ export function DirectoryList({ directory, showHidden = false, folderClassname,
|
||||||
<ImagePreview source={file.getIconUrl()}/>
|
<ImagePreview source={file.getIconUrl()}/>
|
||||||
</div>
|
</div>
|
||||||
<p>{file.id}</p>
|
<p>{file.id}</p>
|
||||||
</button>
|
</Interactable>
|
||||||
)}
|
)}
|
||||||
</>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
@ -1,3 +1,9 @@
|
||||||
|
.Container {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.File-button, .Folder-button {
|
.File-button, .Folder-button {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
|
|
@ -15,11 +21,16 @@
|
||||||
transition: background-color 100ms ease-in-out;
|
transition: background-color 100ms ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.File-button[data-selected=true],
|
||||||
|
.Folder-button[data-selected=true] {
|
||||||
|
background-color: hsla(var(--background-color-a-hsl), 40%) !important;
|
||||||
|
}
|
||||||
|
|
||||||
.File-button:hover,
|
.File-button:hover,
|
||||||
.Folder-button:hover,
|
.Folder-button:hover,
|
||||||
.File-button:focus-visible,
|
.File-button:focus-visible,
|
||||||
.Folder-button:focus-visible {
|
.Folder-button:focus-visible {
|
||||||
background-color: hsla(var(--background-color-a-hsl), 35%);
|
background-color: hsla(var(--background-color-a-hsl), 20%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.File-button p, .Folder-button p {
|
.File-button p, .Folder-button p {
|
||||||
|
|
@ -37,4 +48,12 @@
|
||||||
width: 50%;
|
width: 50%;
|
||||||
height: auto;
|
height: auto;
|
||||||
aspect-ratio: 1;
|
aspect-ratio: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Selection-rect {
|
||||||
|
opacity: 25%;
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
background-color: var(--blue-b);
|
||||||
|
border: 0.25rem solid var(--blue-a);
|
||||||
}
|
}
|
||||||
|
|
@ -17,7 +17,7 @@ export function MediaViewer({ file, close, setTitle }) {
|
||||||
|
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
windowsManager.open(APPS.FILE_EXPLORER, { startPath: "~/Images" });
|
windowsManager.open(APPS.FILE_EXPLORER, { startPath: "~/Pictures" });
|
||||||
close();
|
close();
|
||||||
}, 10);
|
}, 10);
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Button } from "../../utils/Button.jsx";
|
import { Button } from "../../utils/button/Button.jsx";
|
||||||
import styles from "./Settings.module.css";
|
import styles from "./Settings.module.css";
|
||||||
import utilStyles from "../../../styles/utils.module.css";
|
import utilStyles from "../../../styles/utils.module.css";
|
||||||
import Vector2 from "../../../features/math/vector2.js";
|
import Vector2 from "../../../features/math/vector2.js";
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import styles from "./Settings.module.css";
|
import styles from "./Settings.module.css";
|
||||||
import utilStyles from "../../../styles/utils.module.css";
|
import utilStyles from "../../../styles/utils.module.css";
|
||||||
import { round } from "../../../features/math/round.js";
|
import { round } from "../../../features/math/round.js";
|
||||||
import { ProgressBar } from "../../utils/ProgressBar.jsx";
|
import { ProgressBar } from "../../utils/progress-bar/ProgressBar.jsx";
|
||||||
import { Button } from "../../utils/Button.jsx";
|
import { Button } from "../../utils/button/Button.jsx";
|
||||||
import { useVirtualRoot } from "../../../hooks/virtual-drive/virtualRootContext.js";
|
import { useVirtualRoot } from "../../../hooks/virtual-drive/virtualRootContext.js";
|
||||||
import { StorageManager } from "../../../features/storage/storageManager.js";
|
import { StorageManager } from "../../../features/storage/storageManager.js";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -85,29 +85,27 @@ export const Desktop = memo(() => {
|
||||||
onContextMenu={onContextMenu}
|
onContextMenu={onContextMenu}
|
||||||
>
|
>
|
||||||
<ModalsView modalsManager={modalsManager} modals={modals}/>
|
<ModalsView modalsManager={modalsManager} modals={modals}/>
|
||||||
<div className={styles.Content}>
|
<DirectoryList
|
||||||
<DirectoryList
|
directory={directory}
|
||||||
directory={directory}
|
className={styles.Content}
|
||||||
fileClassname={styles["Item"]}
|
fileClassName={styles["Item"]}
|
||||||
folderClassname={styles["Item"]}
|
folderClassName={styles["Item"]}
|
||||||
onClickFile={(event, file) => {
|
onClickFile={(event, file) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const options = {};
|
const options = { mode: "view" };
|
||||||
if (file.name === "info.md") {
|
if (file.name === "info.md") {
|
||||||
options.mode = "view";
|
options.size = new Vector2(575, 675);
|
||||||
options.size = new Vector2(575, 675);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
windowsManager.openFile(file, options);
|
windowsManager.openFile(file, options);
|
||||||
}}
|
}}
|
||||||
onClickFolder={(event, { linkedPath, path }) => {
|
onClickFolder={(event, { linkedPath, path }) => {
|
||||||
windowsManager.open(APPS.FILE_EXPLORER, { startPath: linkedPath ?? path });
|
windowsManager.open(APPS.FILE_EXPLORER, { startPath: linkedPath ?? path });
|
||||||
}}
|
}}
|
||||||
onContextMenuFile={onContextMenuFile}
|
onContextMenuFile={onContextMenuFile}
|
||||||
onContextMenuFolder={onContextMenuFolder}
|
onContextMenuFolder={onContextMenuFolder}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
{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
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ export function HomeMenu({ active, setActive, search }) {
|
||||||
</button>
|
</button>
|
||||||
<button title="Images" tabIndex={tabIndex} onClick={() => {
|
<button title="Images" tabIndex={tabIndex} onClick={() => {
|
||||||
setActive(false);
|
setActive(false);
|
||||||
windowsManager.open(APPS.FILE_EXPLORER, { startPath: "~/Images" });
|
windowsManager.open(APPS.FILE_EXPLORER, { startPath: "~/Pictures" });
|
||||||
}}>
|
}}>
|
||||||
<FontAwesomeIcon icon={faImage}/>
|
<FontAwesomeIcon icon={faImage}/>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import styles from "./DropdownButton.module.css";
|
import styles from "./DropdownButton.module.css";
|
||||||
import OutsideClickListener from "../../hooks/utils/outsideClick.js";
|
import OutsideClickListener from "../../../hooks/utils/outsideClick.js";
|
||||||
import { formatShortcut } from "../../features/utils/string.js";
|
import { formatShortcut } from "../../../features/utils/string.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} props
|
* @param {object} props
|
||||||
34
src/components/utils/interactable/Interactable.jsx
Normal file
34
src/components/utils/interactable/Interactable.jsx
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
import { INTERACTIBLE_DOUBLE_CLICK_DELAY } from "../../../constants/utils.js";
|
||||||
|
|
||||||
|
let timeoutId = null;
|
||||||
|
|
||||||
|
export function Interactable({ onClick, onDoubleClick, children, ...props }) {
|
||||||
|
const [clicked, setClicked] = useState(false);
|
||||||
|
|
||||||
|
const onButtonClick = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
if (timeoutId != null)
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
|
if (clicked) {
|
||||||
|
setClicked(false);
|
||||||
|
onDoubleClick?.(event);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setClicked(true);
|
||||||
|
onClick?.(event);
|
||||||
|
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
setClicked(false);
|
||||||
|
}, INTERACTIBLE_DOUBLE_CLICK_DELAY);
|
||||||
|
};
|
||||||
|
|
||||||
|
return <button {...props} onClick={onButtonClick}>
|
||||||
|
{children}
|
||||||
|
</button>;
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { clamp } from "../../features/math/clamp.js";
|
import { clamp } from "../../../features/math/clamp.js";
|
||||||
import styles from "./ProgressBar.module.css";
|
import styles from "./ProgressBar.module.css";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1 +1 @@
|
||||||
export const WALLPAPERS_PATH = "~/Images/Wallpapers";
|
export const WALLPAPERS_PATH = "~/Pictures/Wallpapers";
|
||||||
1
src/constants/utils.js
Normal file
1
src/constants/utils.js
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export const INTERACTIBLE_DOUBLE_CLICK_DELAY = 250;
|
||||||
|
|
@ -15,7 +15,7 @@ export default class Application {
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowContent = (props) => {
|
WindowContent = (props) => {
|
||||||
props = {...props, ...this.windowOptions};
|
props = { ...props, ...this.windowOptions };
|
||||||
|
|
||||||
if (this.windowContent == null) {
|
if (this.windowContent == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
117
src/features/virtual-drive/defaultData.js
Normal file
117
src/features/virtual-drive/defaultData.js
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
import { APPS } from "../../constants/applications.js";
|
||||||
|
import { WALLPAPERS } from "../../constants/desktop.js";
|
||||||
|
import AppsManager from "../applications/applications.js";
|
||||||
|
import { VirtualRoot } from "./virtualRoot.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads default data on the virtual root
|
||||||
|
* @param {VirtualRoot} virtualRoot
|
||||||
|
*/
|
||||||
|
export function loadDefaultData(virtualRoot) {
|
||||||
|
virtualRoot.createFolder("bin", (folder) => {
|
||||||
|
folder.createFiles([
|
||||||
|
{ name: "echo" },
|
||||||
|
{ name: "cd" },
|
||||||
|
{ name: "ls" },
|
||||||
|
{ name: "clear" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
virtualRoot.createFolder("dev", (folder) => {
|
||||||
|
folder.createFiles([
|
||||||
|
{ name: "null" },
|
||||||
|
{ name: "zero" },
|
||||||
|
{ name: "random" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
virtualRoot.createFolder("etc");
|
||||||
|
|
||||||
|
virtualRoot.createFolder("usr", (folder) => {
|
||||||
|
folder.createFolders(["bin", "sbin", "lib", "share"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
const linkedPaths = {};
|
||||||
|
|
||||||
|
virtualRoot.createFolder("home", (folder) => {
|
||||||
|
folder.createFolder("prozilla-os", (folder) => {
|
||||||
|
folder.setAlias("~")
|
||||||
|
.createFolder(".config", (folder) => {
|
||||||
|
folder.createFile("desktop", "xml", (file) => {
|
||||||
|
file.setSource("/config/desktop.xml");
|
||||||
|
}).createFile("taskbar", "xml", (file) => {
|
||||||
|
file.setSource("/config/taskbar.xml");
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.createFolder("Pictures", (folder) => {
|
||||||
|
folder.setIconUrl(AppsManager.getAppIconUrl(APPS.FILE_EXPLORER, "folder-images"));
|
||||||
|
folder.createFolder("Wallpapers", (folder) => {
|
||||||
|
folder.setProtected(true);
|
||||||
|
for (let i = 0; i < WALLPAPERS.length; i++) {
|
||||||
|
const source = WALLPAPERS[i];
|
||||||
|
folder.createFile(`Wallpaper${i + 1}`, "png", (file) => {
|
||||||
|
file.setSource(source);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).createFile("ProzillaOS", "png", (file) => {
|
||||||
|
file.setSource("/assets/banner-logo-title.png");
|
||||||
|
}).createFolder("Crumbling City", (folder) => {
|
||||||
|
folder.createFile("Japan", "png", (file) => {
|
||||||
|
file.setSource("https://daisygames.org/media/Games/Crumbling%20City/CrumblingCityRelease.png");
|
||||||
|
}).createFile("City Center", "png", (file) => {
|
||||||
|
file.setSource("https://daisygames.org/media/Games/Crumbling%20City/Screenshot_City_Firegun.png");
|
||||||
|
}).createFile("Farms", "png", (file) => {
|
||||||
|
file.setSource("https://daisygames.org/media/Games/Crumbling%20City/Screenshot_Farms_Hammer.png");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
linkedPaths.images = folder.path;
|
||||||
|
})
|
||||||
|
.createFolder("Documents", (folder) => {
|
||||||
|
folder.setIconUrl(AppsManager.getAppIconUrl(APPS.FILE_EXPLORER, "folder-text"));
|
||||||
|
folder.createFile("text", "txt", (file) => {
|
||||||
|
file.setContent("Hello world!");
|
||||||
|
}).createFile("info", "md", (file) => {
|
||||||
|
file.setProtected(true)
|
||||||
|
.setSource("/documents/info.md")
|
||||||
|
.setIconUrl(AppsManager.getAppIconUrl(APPS.FILE_EXPLORER, "file-info"));
|
||||||
|
linkedPaths.info = file.path;
|
||||||
|
}).createFile("links", "md", (file) => {
|
||||||
|
file.setProtected(true)
|
||||||
|
.setSource("/documents/links.md");
|
||||||
|
linkedPaths.links = file.path;
|
||||||
|
});
|
||||||
|
linkedPaths.documents = folder.path;
|
||||||
|
})
|
||||||
|
.createFolder("Desktop", (folder) => {
|
||||||
|
folder.createFileLink("info.md", (fileLink) => {
|
||||||
|
fileLink.setLinkedPath(linkedPaths.info);
|
||||||
|
}).createFileLink("links.md", (fileLink) => {
|
||||||
|
fileLink.setLinkedPath(linkedPaths.links);
|
||||||
|
}).createFolderLink("Pictures", (folderLink) => {
|
||||||
|
folderLink.setLinkedPath(linkedPaths.images);
|
||||||
|
}).createFolderLink("Documents", (folderLink) => {
|
||||||
|
folderLink.setLinkedPath(linkedPaths.documents);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
virtualRoot.createFolder("lib");
|
||||||
|
virtualRoot.createFolder("sbin");
|
||||||
|
virtualRoot.createFolder("tmp");
|
||||||
|
virtualRoot.createFolder("var");
|
||||||
|
virtualRoot.createFolder("boot");
|
||||||
|
|
||||||
|
virtualRoot.createFolder("proc", (folder) => {
|
||||||
|
folder.createFiles([
|
||||||
|
{ name: "cpuinfo" },
|
||||||
|
{ name: "meminfo" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
virtualRoot.createFolder("var");
|
||||||
|
virtualRoot.createFolder("opt");
|
||||||
|
virtualRoot.createFolder("media");
|
||||||
|
virtualRoot.createFolder("mnt");
|
||||||
|
virtualRoot.createFolder("srv");
|
||||||
|
}
|
||||||
|
|
@ -32,7 +32,7 @@ export class VirtualBase extends EventEmitter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} alias
|
* @param {string} alias
|
||||||
* @returns {ThisType}
|
* @returns {VirtualBase}
|
||||||
*/
|
*/
|
||||||
setAlias(alias) {
|
setAlias(alias) {
|
||||||
if (this.alias === alias || !this.canBeEdited)
|
if (this.alias === alias || !this.canBeEdited)
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ export class VirtualFolder extends VirtualBase {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} alias
|
* @param {string} alias
|
||||||
* @returns {ThisType}
|
* @returns {VirtualFolder}
|
||||||
*/
|
*/
|
||||||
setAlias(alias) {
|
setAlias(alias) {
|
||||||
return super.setAlias(alias);
|
return super.setAlias(alias);
|
||||||
|
|
@ -39,7 +39,7 @@ export class VirtualFolder extends VirtualBase {
|
||||||
* Returns true if this folder contains a file matching a name and extension
|
* Returns true if this folder contains a file matching a name and extension
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
* @param {string} extension
|
* @param {string} extension
|
||||||
* @returns {ThisType}
|
* @returns {VirtualFolder}
|
||||||
*/
|
*/
|
||||||
hasFile(name, extension) {
|
hasFile(name, extension) {
|
||||||
return this.findFile(name, extension) !== null;
|
return this.findFile(name, extension) !== null;
|
||||||
|
|
@ -130,7 +130,7 @@ export class VirtualFolder extends VirtualBase {
|
||||||
if (!this.canBeEdited)
|
if (!this.canBeEdited)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
files.forEach(({name, extension}) => {
|
files.forEach(({ name, extension }) => {
|
||||||
this.createFile(name, extension);
|
this.createFile(name, extension);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import { APPS } from "../../constants/applications.js";
|
|
||||||
import { WALLPAPERS } from "../../constants/desktop.js";
|
|
||||||
import AppsManager from "../applications/applications.js";
|
|
||||||
import { StorageManager } from "../storage/storageManager.js";
|
import { StorageManager } from "../storage/storageManager.js";
|
||||||
import { VirtualFolderLink } from "./VirtualFolderLink.js";
|
import { VirtualFolderLink } from "./VirtualFolderLink.js";
|
||||||
|
import { loadDefaultData } from "./defaultData.js";
|
||||||
import { VirtualFile } from "./virtualFile.js";
|
import { VirtualFile } from "./virtualFile.js";
|
||||||
import { VirtualFolder } from "./virtualFolder.js";
|
import { VirtualFolder } from "./virtualFolder.js";
|
||||||
|
|
||||||
|
|
@ -24,98 +22,7 @@ export class VirtualRoot extends VirtualFolder {
|
||||||
}
|
}
|
||||||
|
|
||||||
loadDefaultData() {
|
loadDefaultData() {
|
||||||
this.createFolder("bin", (folder) => {
|
loadDefaultData(this);
|
||||||
folder.createFiles([
|
|
||||||
{ name: "echo" },
|
|
||||||
{ name: "cd" },
|
|
||||||
{ name: "ls" },
|
|
||||||
{ name: "clear" },
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.createFolder("dev", (folder) => {
|
|
||||||
folder.createFiles([
|
|
||||||
{ name: "null" },
|
|
||||||
{ name: "zero" },
|
|
||||||
{ name: "random" },
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.createFolder("etc");
|
|
||||||
|
|
||||||
this.createFolder("usr", (folder) => {
|
|
||||||
folder.createFolders(["bin", "sbin", "lib", "share"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
const linkedPaths = {};
|
|
||||||
|
|
||||||
this.createFolder("home", (folder) => {
|
|
||||||
folder.createFolder("prozilla-os", (folder) => {
|
|
||||||
folder.setAlias("~")
|
|
||||||
.createFolder(".config", (folder) => {
|
|
||||||
folder.createFile("desktop", "xml", (file) => {
|
|
||||||
file.setSource("/config/desktop.xml");
|
|
||||||
}).createFile("taskbar", "xml", (file) => {
|
|
||||||
file.setSource("/config/taskbar.xml");
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.createFolder("Images", (folder) => {
|
|
||||||
folder.setIconUrl(AppsManager.getAppIconUrl(APPS.FILE_EXPLORER, "folder-images"));
|
|
||||||
folder.createFolder("Wallpapers", (folder) => {
|
|
||||||
folder.setProtected(true);
|
|
||||||
for (let i = 0; i < WALLPAPERS.length; i++) {
|
|
||||||
const source = WALLPAPERS[i];
|
|
||||||
folder.createFile(`Wallpaper${i + 1}`, "png", (file) => {
|
|
||||||
file.setSource(source);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).createFile("ProzillaOS", "png", (file) => {
|
|
||||||
file.setSource("/assets/banner-logo-title.png");
|
|
||||||
});
|
|
||||||
linkedPaths.images = folder.path;
|
|
||||||
})
|
|
||||||
.createFolder("Documents", (folder) => {
|
|
||||||
folder.setIconUrl(AppsManager.getAppIconUrl(APPS.FILE_EXPLORER, "folder-text"));
|
|
||||||
folder.createFile("text", "txt", (file) => {
|
|
||||||
file.setContent("Hello world!");
|
|
||||||
}).createFile("info", "md", (file) => {
|
|
||||||
file.setProtected(true)
|
|
||||||
.setSource("/documents/info.md")
|
|
||||||
.setIconUrl(AppsManager.getAppIconUrl(APPS.FILE_EXPLORER, "file-info"));
|
|
||||||
linkedPaths.info = file.path;
|
|
||||||
});
|
|
||||||
linkedPaths.documents = folder.path;
|
|
||||||
})
|
|
||||||
.createFolder("Desktop", (folder) => {
|
|
||||||
folder.createFileLink("info.md", (fileLink) => {
|
|
||||||
fileLink.setLinkedPath(linkedPaths.info);
|
|
||||||
}).createFolderLink("Images", (folderLink) => {
|
|
||||||
folderLink.setLinkedPath(linkedPaths.images);
|
|
||||||
}).createFolderLink("Documents", (folderLink) => {
|
|
||||||
folderLink.setLinkedPath(linkedPaths.documents);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.createFolder("lib");
|
|
||||||
this.createFolder("sbin");
|
|
||||||
this.createFolder("tmp");
|
|
||||||
this.createFolder("var");
|
|
||||||
this.createFolder("boot");
|
|
||||||
|
|
||||||
this.createFolder("proc", (folder) => {
|
|
||||||
folder.createFiles([
|
|
||||||
{ name: "cpuinfo" },
|
|
||||||
{ name: "meminfo" },
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.createFolder("var");
|
|
||||||
this.createFolder("opt");
|
|
||||||
this.createFolder("media");
|
|
||||||
this.createFolder("mnt");
|
|
||||||
this.createFolder("srv");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loadData() {
|
loadData() {
|
||||||
|
|
@ -132,7 +39,7 @@ export class VirtualRoot extends VirtualFolder {
|
||||||
if (object == null)
|
if (object == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const shortcuts = {...object.scs};
|
const shortcuts = { ...object.scs };
|
||||||
|
|
||||||
const addFile = ({
|
const addFile = ({
|
||||||
nam: name,
|
nam: name,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue