diff --git a/src/components/applications/file-explorer/FileExplorer.jsx b/src/components/applications/file-explorer/FileExplorer.jsx
index 0c6d094..accae59 100644
--- a/src/components/applications/file-explorer/FileExplorer.jsx
+++ b/src/components/applications/file-explorer/FileExplorer.jsx
@@ -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) =>
{
@@ -31,6 +38,24 @@ export function FileExplorer({ startPath, app, modalsManager }) {
{
file.delete();
}}/>
+ {
+ openWindowedModal({
+ title: `${file.id} ${TITLE_SEPARATOR} Properties`,
+ iconUrl: file.getIconUrl(),
+ size: new Vector2(400, 500),
+ Modal: (props) =>
+
+ });
+ // modalsManager.open(new Modal(FileProperties)
+ // .setPosition(new Vector2(positionX, positionY))
+ // .setProps({
+ // triggerParams: params,
+ // className: STYLES.CONTEXT_MENU,
+ // onAnyTrigger: () => {
+ // newModal.close();
+ // }
+ // }));
+ }}/>
});
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 (
@@ -97,15 +123,16 @@ export function FileExplorer({ startPath, app, modalsManager }) {
+
+
+ {itemCount === 1
+ ? itemCount + " item"
+ : itemCount + " items"
+ }
+
+
);
}
\ No newline at end of file
diff --git a/src/components/applications/file-explorer/FileExplorer.module.css b/src/components/applications/file-explorer/FileExplorer.module.css
index 44c2b19..94cdb7a 100644
--- a/src/components/applications/file-explorer/FileExplorer.module.css
+++ b/src/components/applications/file-explorer/FileExplorer.module.css
@@ -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;
}
\ No newline at end of file
diff --git a/src/components/applications/file-explorer/directory-list/DirectoryList.jsx b/src/components/applications/file-explorer/directory-list/DirectoryList.jsx
index 6df84cd..bc67dff 100644
--- a/src/components/applications/file-explorer/directory-list/DirectoryList.jsx
+++ b/src/components/applications/file-explorer/directory-list/DirectoryList.jsx
@@ -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);
}}
>
-
+
+
+
{folder.name}
)}
@@ -69,7 +70,9 @@ export function DirectoryList({ directory, showHidden = false, folderClassname,
onClickFile?.(event, file);
}}
>
-
+
+
+
{file.id}
)}
diff --git a/src/components/applications/file-explorer/directory-list/FileIcon.jsx b/src/components/applications/file-explorer/directory-list/FileIcon.jsx
deleted file mode 100644
index e54ee45..0000000
--- a/src/components/applications/file-explorer/directory-list/FileIcon.jsx
+++ /dev/null
@@ -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 = ;
- } else {
- switch (file.extension) {
- case "png":
- preview = ;
- break;
- case "txt":
- case "md":
- preview = ;
- break;
- case "xml":
- preview = ;
- break;
- default:
- preview = ;
- break;
- }
- }
-
- return (
- {preview}
-
);
-}
\ No newline at end of file
diff --git a/src/components/applications/file-explorer/directory-list/FolderIcon.jsx b/src/components/applications/file-explorer/directory-list/FolderIcon.jsx
deleted file mode 100644
index b159e14..0000000
--- a/src/components/applications/file-explorer/directory-list/FolderIcon.jsx
+++ /dev/null
@@ -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 = ;
- } else if (folder.linkedPath) {
- preview = ;
- } else {
- preview = ;
- }
-
- return (
- {preview}
-
);
-}
\ No newline at end of file
diff --git a/src/components/applications/file-explorer/directory-list/ImagePreview.jsx b/src/components/applications/file-explorer/directory-list/ImagePreview.jsx
index d9d62ca..65e83d3 100644
--- a/src/components/applications/file-explorer/directory-list/ImagePreview.jsx
+++ b/src/components/applications/file-explorer/directory-list/ImagePreview.jsx
@@ -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 ;
- return (
+ const classNames = [styles["Image-preview"]];
+ if (className != null)
+ classNames.push(className);
+
+ return (
{source.endsWith(".svg")
?
:
{ setLoadingFailed(true); }} alt="Preview" draggable="false"/>
diff --git a/src/components/applications/text-editor/TextEditor.jsx b/src/components/applications/text-editor/TextEditor.jsx
index 66af1e3..27afba5 100644
--- a/src/components/applications/text-editor/TextEditor.jsx
+++ b/src/components/applications/text-editor/TextEditor.jsx
@@ -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 = () => {
diff --git a/src/components/modals/.templates/WindowedModal.jsx b/src/components/modals/.templates/WindowedModal.jsx
new file mode 100644
index 0000000..62b9512
--- /dev/null
+++ b/src/components/modals/.templates/WindowedModal.jsx
@@ -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 (
+
+
+
+
{title}
+
{ modal.close(); }}>
+
+
+
+
+ {children}
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/modals/.templates/WindowedModal.module.css b/src/components/modals/.templates/WindowedModal.module.css
new file mode 100644
index 0000000..f37055a
--- /dev/null
+++ b/src/components/modals/.templates/WindowedModal.module.css
@@ -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);
+}
\ No newline at end of file
diff --git a/src/components/modals/dialog-box/DialogBox.jsx b/src/components/modals/dialog-box/DialogBox.jsx
index 32126d7..6fae257 100644
--- a/src/components/modals/dialog-box/DialogBox.jsx
+++ b/src/components/modals/dialog-box/DialogBox.jsx
@@ -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 (
-
-
-
-
{title}
-
{ modal.close(); }}>
-
-
-
-
- {children}
-
-
- );
+ return
+ {children}
+ ;
}
\ No newline at end of file
diff --git a/src/components/modals/file-properties/FileProperties.jsx b/src/components/modals/file-properties/FileProperties.jsx
new file mode 100644
index 0000000..abaf2f5
--- /dev/null
+++ b/src/components/modals/file-properties/FileProperties.jsx
@@ -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
+
+
+ {file.id}
+
+
+ Type: {file.getType()}
+
+ Opens with:
+
+ {associatedApp.name}
+
+
+
+ Location: {file.path}
+ Size: {StorageManager.getByteSize(file.source ?? file.content)} bytes
+ Size on drive: {StorageManager.getByteSize(file.toString())} bytes
+
+
+ Attributes: {file.isProtected ? "Protected" : "N/A"}
+
+ ;
+}
\ No newline at end of file
diff --git a/src/components/modals/file-properties/FileProperties.module.css b/src/components/modals/file-properties/FileProperties.module.css
new file mode 100644
index 0000000..80adc48
--- /dev/null
+++ b/src/components/modals/file-properties/FileProperties.module.css
@@ -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%;
+}
\ No newline at end of file
diff --git a/src/components/taskbar/Taskbar.jsx b/src/components/taskbar/Taskbar.jsx
index 8fa0106..937016c 100644
--- a/src/components/taskbar/Taskbar.jsx
+++ b/src/components/taskbar/Taskbar.jsx
@@ -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 ( {
active={isActive}
visible={shouldBeShown}
/>);
- }), [modalsManager, pins, windows]);
+ }), [modalsManager, pins, windows, windowsManager]);
useEffect(() => {
(async () => {
diff --git a/src/components/taskbar/app-icon/AppIcon.jsx b/src/components/taskbar/app-icon/AppIcon.jsx
index c2d1044..5c48071 100644
--- a/src/components/taskbar/app-icon/AppIcon.jsx
+++ b/src/components/taskbar/app-icon/AppIcon.jsx
@@ -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) =>
@@ -49,6 +47,9 @@ export const AppButton = memo(({ app, modalsManager, pins, active, visible }) =>
if (!visible)
classNames.push(styles.Hidden);
+ if (!windowsManager)
+ return;
+
return (
{
- 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)
diff --git a/src/features/virtual-drive/virtualFile.js b/src/features/virtual-drive/virtualFile.js
index 195c3d0..97eb0b6 100644
--- a/src/features/virtual-drive/virtualFile.js
+++ b/src/features/virtual-drive/virtualFile.js
@@ -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}
*/
diff --git a/src/features/virtual-drive/virtualFolder.js b/src/features/virtual-drive/virtualFolder.js
index 1fa9638..60c7d87 100644
--- a/src/features/virtual-drive/virtualFolder.js
+++ b/src/features/virtual-drive/virtualFolder.js
@@ -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}
+ * @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}
+ * @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}
*/
diff --git a/src/hooks/modals/ContextMenu.js b/src/hooks/modals/ContextMenu.js
index da3084a..19d4b6e 100644
--- a/src/hooks/modals/ContextMenu.js
+++ b/src/hooks/modals/ContextMenu.js
@@ -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}
diff --git a/src/hooks/modals/dialogBox.js b/src/hooks/modals/windowedModal.js
similarity index 51%
rename from src/hooks/modals/dialogBox.js
rename to src/hooks/modals/windowedModal.js
index 48bcee4..3f2186b 100644
--- a/src/hooks/modals/dialogBox.js
+++ b/src/hooks/modals/windowedModal.js
@@ -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 };
}
\ No newline at end of file
diff --git a/src/styles/global/variables.css b/src/styles/global/variables.css
index 9d3975d..3159fb0 100644
--- a/src/styles/global/variables.css
+++ b/src/styles/global/variables.css
@@ -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%);