Added file properties modal

This commit is contained in:
Prozilla 2023-12-16 18:10:46 +01:00
parent b7bc57d9ce
commit 7334b6ceb5
No known key found for this signature in database
GPG key ID: 5858DFE71CAF31EE
23 changed files with 558 additions and 174 deletions

View file

@ -2,16 +2,22 @@ import { useState } from "react";
import { useVirtualRoot } from "../../../hooks/virtual-drive/virtualRootContext.js";
import styles from "./FileExplorer.module.css";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faArrowUp, faCaretLeft, faCaretRight, faCog, faDesktop, faFileLines, faHouse, faImage, faPlus, faSearch, faTrash } from "@fortawesome/free-solid-svg-icons";
import { faArrowUp, faCaretLeft, faCaretRight, faCircleInfo, faCog, faDesktop, faFileLines, faHouse, faImage, faPlus, faSearch, faTrash } from "@fortawesome/free-solid-svg-icons";
import { useWindowsManager } from "../../../hooks/windows/windowsManagerContext.js";
import { useContextMenu } from "../../../hooks/modals/contextMenu.js";
import { QuickAccessButton } from "./QuickAccessButton.jsx";
import { useDialogBox } from "../../../hooks/modals/dialogBox.js";
import { useWindowedModal } from "../../../hooks/modals/windowedModal.js";
import Vector2 from "../../../features/math/vector2.js";
import { DIALOG_CONTENT_TYPES } from "../../../constants/modals.js";
import { DirectoryList } from "./directory-list/DirectoryList.jsx";
import { Actions } from "../../actions/Actions.jsx";
import { ClickAction } from "../../actions/actions/ClickAction.jsx";
import utilStyles from "../../../styles/utils.module.css";
import { DialogBox } from "../../modals/dialog-box/DialogBox.jsx";
import AppsManager from "../../../features/applications/applications.js";
import { APPS } from "../../../constants/applications.js";
import { TITLE_SEPARATOR } from "../../../constants/windows.js";
import { FileProperties } from "../../modals/file-properties/FileProperties.jsx";
/**
* @param {import("../../windows/WindowView.jsx").windowProps} props
@ -23,6 +29,7 @@ export function FileExplorer({ startPath, app, modalsManager }) {
const windowsManager = useWindowsManager();
const [showHidden] = useState(true);
const { openWindowedModal } = useWindowedModal({ modalsManager });
const { onContextMenu: onContextMenuFile } = useContextMenu({ modalsManager, Actions: (props) =>
<Actions {...props}>
<ClickAction label="Open" onTrigger={(event, file) => {
@ -31,6 +38,24 @@ export function FileExplorer({ startPath, app, modalsManager }) {
<ClickAction label="Delete" icon={faTrash} onTrigger={(event, file) => {
file.delete();
}}/>
<ClickAction label="Properties" icon={faCircleInfo} onTrigger={(event, file) => {
openWindowedModal({
title: `${file.id} ${TITLE_SEPARATOR} Properties`,
iconUrl: file.getIconUrl(),
size: new Vector2(400, 500),
Modal: (props) =>
<FileProperties file={file} {...props}/>
});
// modalsManager.open(new Modal(FileProperties)
// .setPosition(new Vector2(positionX, positionY))
// .setProps({
// triggerParams: params,
// className: STYLES.CONTEXT_MENU,
// onAnyTrigger: () => {
// newModal.close();
// }
// }));
}}/>
</Actions>
});
const { onContextMenu: onContextMenuFolder } = useContextMenu({ modalsManager, Actions: (props) =>
@ -50,7 +75,6 @@ export function FileExplorer({ startPath, app, modalsManager }) {
// "Folder": () => { currentDirectory.createFolder("New Folder"); }
// }
// });
const { onDialogBox } = useDialogBox({ modalsManager });
const changeDirectory = (path, absolute = false) => {
if (currentDirectory == null)
@ -84,6 +108,8 @@ export function FileExplorer({ startPath, app, modalsManager }) {
}
};
const itemCount = currentDirectory.getItemCount(showHidden);
return (
<div className={styles.Container}>
<div className={styles.Header}>
@ -97,15 +123,16 @@ export function FileExplorer({ startPath, app, modalsManager }) {
<FontAwesomeIcon icon={faArrowUp}/>
</button>
<button title="New" tabIndex={0} className={styles["Icon-button"]}
onClick={(event) => {
onDialogBox(event, {
app,
onClick={() => {
openWindowedModal({
title: "Error",
iconUrl: AppsManager.getAppIconUrl(APPS.FILE_EXPLORER),
size: new Vector2(300, 150),
children: <>
<p>This folder is protected.</p>
<button data-type={DIALOG_CONTENT_TYPES.CloseButton}>Ok</button>
</>
Modal: (props) =>
<DialogBox {...props}>
<p>This folder is protected.</p>
<button data-type={DIALOG_CONTENT_TYPES.CloseButton}>Ok</button>
</DialogBox>
});
// if (currentDirectory.canBeEdited) {
@ -156,6 +183,14 @@ export function FileExplorer({ startPath, app, modalsManager }) {
/>
</div>
</div>
<span className={styles.Footer}>
<p className={utilStyles["Text-light"]}>
{itemCount === 1
? itemCount + " item"
: itemCount + " items"
}
</p>
</span>
</div>
);
}

View file

@ -112,6 +112,7 @@
}
.Main {
position: relative;
flex: 1;
display: flex;
flex-wrap: wrap;
@ -119,4 +120,23 @@
height: 100%;
padding: 0.5rem;
overflow: auto;
}
.Footer {
position: absolute;
display: flex;
justify-content: flex-start;
align-items: center;
bottom: 0;
left: 0;
width: 100%;
height: 1.75rem;
padding: 0 0.75rem;
border-top: 0.25rem solid var(--background-color-a);
background-color: var(--background-color-c);
}
.Footer p {
margin: 0;
font-size: 0.875em;
}

View file

@ -1,8 +1,7 @@
import { VirtualFile } from "../../../../features/virtual-drive/virtualFile.js";
import { VirtualFolder } from "../../../../features/virtual-drive/virtualFolder.js";
import { FileIcon } from "./FileIcon.jsx";
import { FolderIcon } from "./FolderIcon.jsx";
import styles from "./DirectoryList.module.css";
import { ImagePreview } from "./ImagePreview.jsx";
/**
* @callback fileEvent
@ -53,7 +52,9 @@ export function DirectoryList({ directory, showHidden = false, folderClassname,
onClickFolder?.(event, folder);
}}
>
<FolderIcon folder={folder} iconUrl={folder.getIconUrl()}/>
<div className={styles["Folder-icon"]}>
<ImagePreview source={folder.getIconUrl()}/>
</div>
<p>{folder.name}</p>
</button>
)}
@ -69,7 +70,9 @@ export function DirectoryList({ directory, showHidden = false, folderClassname,
onClickFile?.(event, file);
}}
>
<FileIcon file={file} iconUrl={file.getIconUrl()}/>
<div className={styles["File-icon"]}>
<ImagePreview source={file.getIconUrl()}/>
</div>
<p>{file.id}</p>
</button>
)}

View file

@ -1,38 +0,0 @@
import styles from "./DirectoryList.module.css";
import { VirtualFile } from "../../../../features/virtual-drive/virtualFile.js";
import { ImagePreview } from "./ImagePreview.jsx";
import AppsManager from "../../../../features/applications/applications.js";
import { APPS } from "../../../../constants/applications.js";
/**
* @param {object} props
* @param {VirtualFile} props.file
* @param {string} props.iconUrl
*/
export function FileIcon({ file, iconUrl }) {
let preview = null;
if (iconUrl && file.extension !== "png") {
preview = <ImagePreview source={iconUrl}/>;
} else {
switch (file.extension) {
case "png":
preview = <ImagePreview source={file.source}/>;
break;
case "txt":
case "md":
preview = <ImagePreview source={AppsManager.getAppIconUrl(APPS.FILE_EXPLORER, "file-text")}/>;
break;
case "xml":
preview = <ImagePreview source={AppsManager.getAppIconUrl(APPS.FILE_EXPLORER, "file-code")}/>;
break;
default:
preview = <ImagePreview source={AppsManager.getAppIconUrl(APPS.FILE_EXPLORER, "file")}/>;
break;
}
}
return (<div className={styles["File-icon"]}>
{preview}
</div>);
}

View file

@ -1,26 +0,0 @@
import styles from "./DirectoryList.module.css";
import { VirtualFolder } from "../../../../features/virtual-drive/virtualFolder.js";
import { ImagePreview } from "./ImagePreview.jsx";
import AppsManager from "../../../../features/applications/applications.js";
import { APPS } from "../../../../constants/applications.js";
/**
* @param {object} props
* @param {VirtualFolder} props.folder
* @param {string} props.iconUrl
*/
export function FolderIcon({ folder, iconUrl }) {
let preview = null;
if (iconUrl) {
preview = <ImagePreview source={iconUrl}/>;
} else if (folder.linkedPath) {
preview = <ImagePreview source={AppsManager.getAppIconUrl(APPS.FILE_EXPLORER, "folder-link")}/>;
} else {
preview = <ImagePreview source={AppsManager.getAppIconUrl(APPS.FILE_EXPLORER, "folder")}/>;
}
return (<div className={styles["Folder-icon"]}>
{preview}
</div>);
}

View file

@ -4,13 +4,17 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faFile } from "@fortawesome/free-solid-svg-icons";
import { ReactSVG } from "react-svg";
export function ImagePreview({ source }) {
export function ImagePreview({ source, className, ...props }) {
const [loadingFailed, setLoadingFailed] = useState(false);
if (loadingFailed)
return <FontAwesomeIcon icon={faFile}/>;
return (<div className={styles["Image-preview"]}>
const classNames = [styles["Image-preview"]];
if (className != null)
classNames.push(className);
return (<div className={classNames.join(" ")} {...props}>
{source.endsWith(".svg")
? <ReactSVG src={source}/>
: <img src={source} onError={() => { setLoadingFailed(true); }} alt="Preview" draggable="false"/>

View file

@ -4,6 +4,7 @@ import { HeaderMenu } from "../.common/HeaderMenu.jsx";
import Markdown from "markdown-to-jsx";
import { DEFAULT_ZOOM, ZOOM_FACTOR } from "../../../constants/applications/textEditor.js";
import AppsManager from "../../../features/applications/applications.js";
import { TITLE_SEPARATOR } from "../../../constants/windows.js";
/**
* @param {import("../../windows/WindowView.jsx").windowProps} props
@ -52,7 +53,7 @@ export function TextEditor({ file, setTitle, setIconUrl, close, mode, app }) {
if (currentMode === "view")
label += " (preview)";
setTitle(`${label} - ${app.name}`);
setTitle(`${label} ${TITLE_SEPARATOR} ${app.name}`);
}, [currentFile, setTitle, unsavedChanges, currentMode, app.name]);
const newText = () => {

View file

@ -0,0 +1,76 @@
import { useEffect, useRef, useState } from "react";
import { useScreenDimensions } from "../../../hooks/utils/screen.js";
import Vector2 from "../../../features/math/vector2.js";
import styles from "./WindowedModal.module.css";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faXmark } from "@fortawesome/free-solid-svg-icons";
import Draggable from "react-draggable";
import { ReactSVG } from "react-svg";
import utilStyles from "../../../styles/utils.module.css";
export function WindowedModal({ modal, params, children, ...props }) {
const { iconUrl, title } = params;
const nodeRef = useRef(null);
const [startPosition, setStartPosition] = useState(modal.position);
const [screenWidth, screenHeight] = useScreenDimensions();
useEffect(() => {
if (screenWidth == null || screenHeight == null)
return;
if (modal.size.x > screenWidth || modal.size.y > screenHeight) {
setStartPosition(new Vector2(0, 0));
} else {
if (modal.position.x > screenWidth) {
modal.position.x = 0;
setStartPosition(modal.position);
}
if (modal.position.y > screenHeight) {
modal.position.y = 0;
setStartPosition(modal.position);
}
}
}, [modal, screenHeight, screenWidth]);
return (<Draggable
axis="both"
handle={".Window-handle"}
defaultPosition={startPosition}
position={null}
scale={1}
bounds={{
top: -modal.position.y - 1,
bottom: screenHeight - 55 - modal.position.y,
left: -modal.size.x + 85 - modal.position.x,
right: screenWidth - 5 - modal.position.x
}}
cancel="button"
nodeRef={nodeRef}
>
<div
className={styles.Container}
ref={nodeRef}
style={{
width: modal.size.x,
height: modal.size.y,
}}
>
<div className={`${styles.Header} Window-handle`}>
<ReactSVG
className={styles["Window-icon"]}
src={iconUrl}
/>
<p className={utilStyles["Text-semibold"]}>{title}</p>
<button aria-label="Close" className={`${styles["Header-button"]} ${styles["Exit-button"]}`} tabIndex={0}
onClick={() => { modal.close(); }}>
<FontAwesomeIcon icon={faXmark}/>
</button>
</div>
<div className={styles["Window-content"]} {...props}>
{children}
</div>
</div>
</Draggable>);
}

View file

@ -0,0 +1,110 @@
.Container {
--header-height: 2.5rem;
--header-button-hover-color: rgba(255, 255, 255, 5%);
position: absolute;
display: flex;
flex-direction: column;
min-width: 300px;
min-height: 150px;
background-color: var(--background-color-c) !important;
resize: both;
overflow: hidden;
}
.Header {
--window-icon-size: 1.5rem;
--window-icon-margin: 0.75rem;
display: flex;
align-items: center;
height: var(--header-height);
padding: 0.25rem;
padding-left: var(--window-icon-margin);
padding-right: 0;
background-color: var(--background-color-b);
cursor: grab;
}
.Window-icon,
.Window-icon > div,
.Window-icon > div > svg {
height: 100%;
width: auto;
}
.Window-icon {
height: var(--window-icon-size);
margin-right: calc(var(--window-icon-margin) - 0.1rem);
}
.Window-icon > div {
display: flex;
align-items: center;
}
.Header > p {
user-select: none;
width: auto;
margin: 0;
margin-right: auto;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.Header-button {
display: flex;
align-items: center;
justify-content: center;
height: var(--header-height);
margin: 0;
padding: 0.75rem;
color: var(--foreground-color-a);
background: none;
cursor: pointer;
border: none;
outline: none;
aspect-ratio: 1;
}
.Header-button > svg {
height: 100%;
}
.Exit-button {
--header-button-hover-color: var(--red-b);
}
.Header-button:hover, .Header-button:focus-visible {
background-color: var(--header-button-hover-color);
}
.Window-content {
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
overflow: hidden;
}
.Window-content button {
margin-bottom: 0.75rem;
padding: 0.5rem 1rem;
width: fit-content;
color: var(--foreground-color-a);
background-color: var(--background-color-a);
border: none;
border-radius: 0.5rem;
outline: none;
transition: background-color 100ms ease-in-out;
cursor: pointer;
}
.Window-content button:hover,
.Window-content button:focus-visible {
background-color: var(--background-color-b);
}

View file

@ -1,40 +1,7 @@
import { useEffect, useRef, useState } from "react";
import styles from "./DialogBox.module.css";
import Draggable from "react-draggable";
import Vector2 from "../../../features/math/vector2.js";
import { ReactSVG } from "react-svg";
import utilStyles from "../../../styles/utils.module.css";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faXmark } from "@fortawesome/free-solid-svg-icons";
import { DIALOG_CONTENT_TYPES } from "../../../constants/modals.js";
import { useScreenDimensions } from "../../../hooks/utils/screen.js";
export function DialogBox({ modal, params }) {
const { app, title, children } = params;
const nodeRef = useRef(null);
const [startPosition, setStartPosition] = useState(modal.position);
const [screenWidth, screenHeight] = useScreenDimensions();
useEffect(() => {
if (screenWidth == null || screenHeight == null)
return;
if (modal.size.x > screenWidth || modal.size.y > screenHeight) {
setStartPosition(new Vector2(0, 0));
} else {
if (modal.position.x > screenWidth) {
modal.position.x = 0;
setStartPosition(modal.position);
}
if (modal.position.y > screenHeight) {
modal.position.y = 0;
setStartPosition(modal.position);
}
}
}, [modal, screenHeight, screenWidth]);
import { WindowedModal } from "../.templates/WindowedModal.jsx";
export function DialogBox({ modal, params, children, ...props }) {
const onClick = (event) => {
event.preventDefault();
const type = parseInt(event.target.getAttribute("data-type"));
@ -46,43 +13,7 @@ export function DialogBox({ modal, params }) {
}
};
return (<Draggable
axis="both"
handle={".Dialog-handle"}
defaultPosition={startPosition}
position={null}
scale={1}
bounds={{
top: -modal.position.y - 1,
bottom: screenHeight - 55 - modal.position.y,
left: -modal.size.x + 85 - modal.position.x,
right: screenWidth - 5 - modal.position.x
}}
cancel="button"
nodeRef={nodeRef}
>
<div
className={styles.Container}
ref={nodeRef}
style={{
width: modal.size.x,
height: modal.size.y,
}}
>
<div className={`${styles.Header} Dialog-handle`}>
<ReactSVG
className={styles["Dialog-icon"]}
src={process.env.PUBLIC_URL + `/assets/applications/icons/${app.id}.svg`}
/>
<p className={utilStyles["Text-semibold"]}>{title}</p>
<button aria-label="Close" className={`${styles["Header-button"]} ${styles["Exit-button"]}`} tabIndex={0} id="close-dialog"
onClick={() => { modal.close(); }}>
<FontAwesomeIcon icon={faXmark}/>
</button>
</div>
<div className={styles["Dialog-content"]} onClick={onClick}>
{children}
</div>
</div>
</Draggable>);
return <WindowedModal modal={modal} params={params} onClick={onClick} {...props}>
{children}
</WindowedModal>;
}

View file

@ -0,0 +1,41 @@
import { ImagePreview } from "../../applications/file-explorer/directory-list/ImagePreview.jsx";
import { WindowedModal } from "../.templates/WindowedModal.jsx";
import styles from "./FileProperties.module.css";
import utilStyles from "../../../styles/utils.module.css";
import Modal from "../../../features/modals/modal.js";
import { VirtualFile } from "../../../features/virtual-drive/virtualFile.js";
import { StorageManager } from "../../../features/storage/storageManager.js";
import AppsManager from "../../../features/applications/applications.js";
/**
* @param {object} props
* @param {Modal} props.modal
* @param {object} props.params
* @param {VirtualFile} props.file
*/
export function FileProperties({ modal, params, file, ...props }) {
const associatedApp = AppsManager.getFileApp(file.extension);
return <WindowedModal className={styles.Container} modal={modal} params={params} {...props}>
<span className={styles["Section"]}>
<ImagePreview className={styles["Icon"]} source={file.getIconUrl()}/>
<p className={`${styles["Line"]} ${utilStyles["Text-bold"]}`}>{file.id}</p>
</span>
<span className={styles["Section"]}>
<p className={styles["Line"]}>Type: {file.getType()}</p>
<p className={styles["Line"]}>
Opens with:
<ImagePreview className={styles["App-icon"]} source={AppsManager.getAppIconUrl(associatedApp.id)}/>
{associatedApp.name}
</p>
</span>
<span className={styles["Section"]}>
<p className={styles["Line"]}>Location: {file.path}</p>
<p className={styles["Line"]}>Size: {StorageManager.getByteSize(file.source ?? file.content)} bytes</p>
<p className={styles["Line"]}>Size on drive: {StorageManager.getByteSize(file.toString())} bytes</p>
</span>
<span className={styles["Section"]}>
<p className={styles["Line"]}>Attributes: {file.isProtected ? "Protected" : "N/A"}</p>
</span>
</WindowedModal>;
}

View file

@ -0,0 +1,90 @@
.Container {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
padding: 1rem;
}
.Icon {
width: 3rem;
height: 3rem;
}
.Icon div {
width: inherit;
height: inherit;
object-fit: contain;
}
.Icon svg {
max-width: 100%;
max-height: 100%;
}
.Section {
--divider-width: 0.25rem;
position: relative;
display: flex;
gap: 0.25rem;
justify-content: flex-start;
align-items: flex-start;
flex-direction: column;
width: 100%;
padding: 1rem 0;
padding-bottom: calc(1rem + var(--divider-width));
}
.Section:first-child {
gap: 0.5rem;
align-items: center;
flex-direction: row;
padding-top: 0;
}
.Section:first-child .Line {
font-size: 1.25rem;
}
.Section:not(:last-child)::after {
content: "";
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: var(--divider-width);
border-radius: 0.25rem;
background-color: var(--background-color-a);
}
.Line {
margin: 0;
text-align: left;
padding-left: 2rem;
text-indent: -2rem;
}
.App-icon {
display: inline-block;
width: 2rem;
height: 2rem;
vertical-align: middle;
margin-left: 0.5rem;
margin-right: 0.25rem;
}
.App-icon div {
position: relative;
width: inherit;
height: inherit;
object-fit: contain;
}
.App-icon svg {
position: absolute;
top: 0;
left: 0;
max-width: 100%;
max-height: 100%;
}

View file

@ -66,6 +66,7 @@ export const Taskbar = memo(() => {
const isActive = windows.map((window) => window.app.id).includes(app.id);
const shouldBeShown = (pins.includes(app.id) || isActive);
return (<AppButton
windowsManager={windowsManager}
modalsManager={modalsManager}
pins={pins}
app={app}
@ -73,7 +74,7 @@ export const Taskbar = memo(() => {
active={isActive}
visible={shouldBeShown}
/>);
}), [modalsManager, pins, windows]);
}), [modalsManager, pins, windows, windowsManager]);
useEffect(() => {
(async () => {

View file

@ -1,6 +1,5 @@
import { memo } from "react";
import Application from "../../../features/applications/application.js";
import { useWindowsManager } from "../../../hooks/windows/windowsManagerContext.js";
import styles from "./AppIcon.module.css";
import { ReactSVG } from "react-svg";
import { useSettingsManager } from "../../../hooks/settings/settingsManagerContext.js";
@ -19,10 +18,9 @@ import { removeFromArray } from "../../../features/utils/array.js";
* @param {string[]} props.pins
* @param {boolean} props.focused
*/
export const AppButton = memo(({ app, modalsManager, pins, active, visible }) => {
export const AppButton = memo(({ app, windowsManager, modalsManager, pins, active, visible }) => {
const isPinned = pins.includes(app.id);
const windowsManager = useWindowsManager();
const settingsManager = useSettingsManager();
const { onContextMenu } = useContextMenu({ modalsManager, Actions: (props) =>
<Actions avoidTaskbar={false} {...props}>
@ -49,6 +47,9 @@ export const AppButton = memo(({ app, modalsManager, pins, active, visible }) =>
if (!visible)
classNames.push(styles.Hidden);
if (!windowsManager)
return;
return (
<button
key={app.id}

View file

@ -1 +1,2 @@
export const SCREEN_MARGIN = 32;
export const SCREEN_MARGIN = 32;
export const TITLE_SEPARATOR = "-";

View file

@ -107,4 +107,9 @@ export class VirtualFileLink extends VirtualFile {
read(...args) {
if (this.isValid()) return this.linkedFile.read(...args);
}
/** @type {VirtualFile["getIconUrl"]} */
getIconUrl(...args) {
if (this.isValid()) return this.iconUrl ?? this.linkedFile.getIconUrl(...args);
}
}

View file

@ -1,3 +1,5 @@
import { APPS } from "../../constants/applications.js";
import AppsManager from "../applications/applications.js";
import { VirtualFolder } from "./virtualFolder.js";
/**
@ -59,6 +61,16 @@ export class VirtualFolderLink extends VirtualFolder {
return (this.linkedFolder != null);
}
/**
* @returns {string}
*/
getIconUrl() {
if (this.iconUrl != null)
return this.iconUrl;
return AppsManager.getAppIconUrl(APPS.FILE_EXPLORER, "folder-link");
}
/**
* @returns {object | null}
*/
@ -135,4 +147,9 @@ export class VirtualFolderLink extends VirtualFolder {
open(...args) {
if (this.isValid()) return this.linkedFolder.open(...args);
}
/** @type {VirtualFolder["getItemCount"]} */
getItemCount(...args) {
if (this.isValid()) return this.linkedFolder.getItemCount(...args);
}
}

View file

@ -1,3 +1,5 @@
import { APPS } from "../../constants/applications.js";
import AppsManager from "../applications/applications.js";
import { EventEmitter } from "../utils/events.js";
import { VirtualRoot } from "./virtualRoot.js";
@ -86,9 +88,19 @@ export class VirtualBase extends EventEmitter {
/**
* @returns {string}
*/
getIconUrl = () => {
return this.iconUrl ?? this.linkedFile?.iconUrl ?? this.linkedFolder?.iconUrl;
};
getIconUrl() {
return this.iconUrl
?? this.linkedFile?.iconUrl
?? this.linkedFolder?.iconUrl
?? AppsManager.getAppIconUrl(APPS.FILE_EXPLORER, "file");
}
/**
* @returns {string}
*/
getType() {
return "None";
}
delete() {
if (!this.canBeEdited)

View file

@ -1,3 +1,5 @@
import { APPS } from "../../constants/applications.js";
import AppsManager from "../applications/applications.js";
import WindowsManager from "../windows/windows.js";
import { VirtualBase } from "./virtualBase.js";
@ -109,6 +111,58 @@ export class VirtualFile extends VirtualBase {
return true;
}
/**
* @returns {string}
*/
getIconUrl() {
if (this.iconUrl != null)
return this.iconUrl;
let iconUrl = null;
switch (this.extension) {
case "png":
iconUrl = this.source;
break;
case "txt":
case "md":
iconUrl = AppsManager.getAppIconUrl(APPS.FILE_EXPLORER, "file-text");
break;
case "xml":
iconUrl = AppsManager.getAppIconUrl(APPS.FILE_EXPLORER, "file-code");
break;
default:
iconUrl = AppsManager.getAppIconUrl(APPS.FILE_EXPLORER, "file");
break;
}
return iconUrl;
}
/**
* @returns {string}
*/
getType() {
let type = "";
switch (this.extension) {
case "png":
type = "PNG Image";
break;
case "txt":
type = "Text";
break;
case "md":
type = "Markdown source";
break;
case "xml":
type = "XML source";
break;
}
return `${type} file (.${this.extension.toLowerCase()})`.trim();
}
/**
* @returns {object | null}
*/

View file

@ -1,4 +1,5 @@
import { APPS } from "../../constants/applications.js";
import AppsManager from "../applications/applications.js";
import { removeFromArray } from "../utils/array.js";
import WindowsManager from "../windows/windows.js";
import { VirtualFileLink } from "./VirtualFileLink.js";
@ -368,7 +369,7 @@ export class VirtualFolder extends VirtualBase {
/**
* Returns all files inside this folder
* @param {Boolean=false} showHidden
* @returns {Array<VirtualFile>}
* @returns {VirtualFile[]}
*/
getFiles(showHidden = false) {
if (showHidden)
@ -380,9 +381,9 @@ export class VirtualFolder extends VirtualBase {
}
/**
* Returns all subfolders inside this folder
* Returns all sub-folders inside this folder
* @param {Boolean=false} showHidden
* @returns {Array<VirtualFolder>}
* @returns {VirtualFolder[]}
*/
getSubFolders(showHidden = false) {
if (showHidden)
@ -393,6 +394,18 @@ export class VirtualFolder extends VirtualBase {
);
}
/**
* Returns the amount of files and sub-folders inside this folder
* @param {Boolean=false} showHidden
* @returns {number}
*/
getItemCount(showHidden = false) {
const filesCount = this.getFiles(showHidden)?.length ?? 0;
const foldersCount = this.getSubFolders(showHidden)?.length ?? 0;
return filesCount + foldersCount;
}
/**
* @returns {boolean}
*/
@ -400,6 +413,16 @@ export class VirtualFolder extends VirtualBase {
return true;
}
/**
* @returns {string}
*/
getIconUrl() {
if (this.iconUrl != null)
return this.iconUrl;
return AppsManager.getAppIconUrl(APPS.FILE_EXPLORER, "folder");
}
/**
* @returns {object | null}
*/

View file

@ -5,7 +5,7 @@ import ModalsManager from "../../features/modals/modals.js";
import { STYLES } from "../../components/actions/Actions.jsx";
/**
* @typedef {Function} onContextMenuType
* @callback onContextMenuType
* @param {object} event
* @param {object} params
* @returns {Modal}

View file

@ -1,11 +1,31 @@
import { useCallback } from "react";
import Modal from "../../features/modals/modal.js";
import { DialogBox } from "../../components/modals/dialog-box/DialogBox.jsx";
import Vector2 from "../../features/math/vector2.js";
import { DEFAULT_DIALOG_SIZE } from "../../constants/modals.js";
import ModalsManager from "../../features/modals/modals.js";
export function useDialogBox({ modalsManager }) {
const onDialogBox = useCallback((event, params = {}) => {
/**
* @callback windowedModalType
* @param {object} props
*/
/**
* @callback openWindowedModalType
* @param {object} params
* @param {string} params.iconUrl
* @param {string} params.title
* @param {Vector2} params.size
* @param {windowedModalType} params.Modal
* @returns {Modal}
*/
/**
* @param {object} props
* @param {ModalsManager} props.modalsManager
* @returns {{ openWindowedModal: openWindowedModalType }}
*/
export function useWindowedModal({ modalsManager }) {
const openWindowedModal = useCallback(({ Modal: WindowedModal, ...params }) => {
const size = params.size ?? DEFAULT_DIALOG_SIZE;
let positionX = (window.innerWidth - size.x) / 4;
let positionY = (window.innerHeight - size.y) / 4;
@ -16,7 +36,9 @@ export function useDialogBox({ modalsManager }) {
positionY -= containerRect.y / 2;
}
const newModal = new Modal(DialogBox)
console.log(WindowedModal);
const newModal = new Modal(WindowedModal)
.setPosition(new Vector2(positionX, positionY))
.setSize(size)
.setDismissible(false)
@ -26,5 +48,5 @@ export function useDialogBox({ modalsManager }) {
return newModal;
}, [modalsManager]);
return { onDialogBox };
return { openWindowedModal };
}

View file

@ -18,8 +18,8 @@
--grey-a: #c8d6e5;
--grey-b: #8395a7;
--dark-grey-a: hsl(211, 14%, 40%);
--dark-grey-b: hsl(212, 29%, 19%);
--dark-grey-c: hsl(212, 29%, 15%);
--dark-grey-b: hsl(var(--background-color-a-hsl));
--dark-grey-c: hsl(var(--background-color-c-hsl));
--dark-grey-ca: hsl(212, 27%, 12%);
--dark-grey-d: hsl(212, 14%, 10%);
--dark-grey-e: hsl(212, 12%, 8%);
@ -35,6 +35,7 @@
--background-color-d: var(--dark-grey-e);
--background-color-a-hsl: 212, 29%, 19%;
--background-color-c-hsl: 212, 29%, 15%;
--scrollbar-color: hsla(211, 29%, 40%, 25%);