diff --git a/index.html b/index.html
index 5267eb2..a44e629 100644
--- a/index.html
+++ b/index.html
@@ -14,7 +14,7 @@
-
+
@@ -70,8 +70,6 @@
]
}
-
-
diff --git a/src/components/actions/Actions.tsx b/src/components/actions/Actions.tsx
index 7d49428..a7db7ef 100644
--- a/src/components/actions/Actions.tsx
+++ b/src/components/actions/Actions.tsx
@@ -9,7 +9,7 @@ export interface ActionProps {
label?: string;
icon?: string | object;
shortcut?: string[];
- onTrigger?: (event: Event, triggerParams: unknown, ...args: unknown[]) => void;
+ onTrigger?: (event?: Event, triggerParams?: unknown, ...args: unknown[]) => void;
children?: ReactNode;
}
@@ -40,8 +40,8 @@ export function Actions({ children, className, onAnyTrigger, triggerParams, avoi
const options = {};
const shortcuts = {};
+ let actionId = 0;
const iterateOverChildren = (children: ReactNode): ReactNode => {
- let actionId = 0;
const newChildren = Children.map(children, (child) => {
if (!isValidElement(child))
return child;
@@ -49,25 +49,30 @@ export function Actions({ children, className, onAnyTrigger, triggerParams, avoi
actionId++;
const { label, shortcut, onTrigger } = child.props as ActionProps;
+
+ const onTriggerOverride = (event: Event, ...args: unknown[]) => {
+ onAnyTrigger?.(event, triggerParams, ...args);
+ onTrigger?.(event, triggerParams, ...args);
+ };
+
+ // Register shortcut
if (label != null && onTrigger != null) {
- options[actionId] = onTrigger;
+ options[actionId] = onTriggerOverride;
if (shortcut != null)
shortcuts[actionId] = shortcut;
}
- if (isListener) {
+ // Prevent listener from rendering
+ if (isListener)
return iterateOverChildren((child.props as ActionProps).children);
- }
return cloneElement(child, {
...child.props,
actionId,
children: iterateOverChildren((child.props as ActionProps).children),
- onTrigger: (event, ...args) => {
- onAnyTrigger?.(event, triggerParams, ...args);
- onTrigger?.(event, triggerParams, ...args);
- }
+ onTrigger: onTriggerOverride
+
} as ActionProps);
});
diff --git a/src/components/desktop/Desktop.module.css b/src/components/desktop/Desktop.module.css
index b108094..d8da9df 100644
--- a/src/components/desktop/Desktop.module.css
+++ b/src/components/desktop/Desktop.module.css
@@ -18,9 +18,11 @@
}
.Content {
+ --direction: column;
+
position: absolute;
display: flex;
- flex-direction: column;
+ flex-direction: var(--direction);
flex-wrap: wrap;
align-content: flex-start;
justify-content: flex-start;
diff --git a/src/components/desktop/Desktop.tsx b/src/components/desktop/Desktop.tsx
index 0f8ced8..cd6e5a0 100644
--- a/src/components/desktop/Desktop.tsx
+++ b/src/components/desktop/Desktop.tsx
@@ -5,7 +5,7 @@ import styles from "./Desktop.module.css";
import { useEffect } from "react";
import { useWindowsManager } from "../../hooks/windows/windowsManagerContext";
import { useContextMenu } from "../../hooks/modals/contextMenu";
-import { FALLBACK_ICON_SIZE, FALLBACK_WALLPAPER } from "../../config/desktop.config";
+import { FALLBACK_ICON_DIRECTION, FALLBACK_ICON_SIZE, FALLBACK_WALLPAPER } from "../../config/desktop.config";
import { reloadViewport } from "../../features/_utils/browser.utils";
import { useVirtualRoot } from "../../hooks/virtual-drive/virtualRootContext";
import { DirectoryList } from "../apps/file-explorer/directory-list/DirectoryList";
@@ -13,7 +13,7 @@ import { APPS, APP_ICONS, APP_NAMES } from "../../config/apps.config";
import Vector2 from "../../features/math/vector2";
import { Actions } from "../actions/Actions";
import { ClickAction } from "../actions/actions/ClickAction";
-import { faArrowsRotate, faEye, faFolder, faPaintBrush, faTerminal, faTrash } from "@fortawesome/free-solid-svg-icons";
+import { faArrowsRotate, faCompress, faExpand, faEye, faFolder, faPaintBrush, faTerminal, faTrash } from "@fortawesome/free-solid-svg-icons";
import { ToggleAction } from "../actions/actions/ToggleAction";
import { DropdownAction } from "../actions/actions/DropdownAction";
import { RadioAction } from "../actions/actions/RadioAction";
@@ -34,6 +34,7 @@ export const Desktop = memo(() => {
const virtualRoot = useVirtualRoot();
const [showIcons, setShowIcons] = useState(false);
const [iconSize, setIconSize] = useState(FALLBACK_ICON_SIZE);
+ const [iconDirection, setIconDirection] = useState(FALLBACK_ICON_DIRECTION);
const { openWindowedModal } = useWindowedModal();
const directory = virtualRoot.navigate("~/Desktop");
@@ -47,7 +48,15 @@ export const Desktop = memo(() => {
}} options={[
{ label: "Small icons" },
{ label: "Medium icons" },
- { label: "Large icons" }
+ { label: "Large icons" },
+ ]}/>
+
+ {
+ const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop);
+ void settings.set("icon-direction", value);
+ }} options={[
+ { label: "Align vertically" },
+ { label: "Align horizontally" },
]}/>
{
@@ -58,6 +67,25 @@ export const Desktop = memo(() => {
{
reloadViewport();
}}/>
+ {
+ if (windowsManager.isAnyFocused())
+ return;
+
+ if (!document.fullscreenElement) {
+ void document.body.requestFullscreen().catch((error) => {
+ console.error(error);
+ });
+ } else {
+ void document.exitFullscreen().catch((error) => {
+ console.error(error);
+ });
+ }
+ }}
+ />
{
windowsManager.open("settings", { tab: TABS.APPEARANCE });
}}/>
@@ -122,6 +150,10 @@ export const Desktop = memo(() => {
if (isValidInteger(value))
setIconSize(parseInt(value));
});
+ void settings.get("icon-direction", (value) => {
+ if (isValidInteger(value))
+ setIconDirection(parseInt(value));
+ });
}, [settingsManager]);
const onError = () => {
@@ -129,7 +161,7 @@ export const Desktop = memo(() => {
void settings.set("wallpaper", FALLBACK_WALLPAPER);
};
- const iconScale = 1 + ((isValidInteger(iconSize) ? iconSize : FALLBACK_ICON_SIZE) - 1) / 4;
+ const iconScale = 1 + ((isValidInteger(iconSize) ? iconSize : FALLBACK_ICON_SIZE) - 1) / 5;
return (<>
@@ -141,7 +173,8 @@ export const Desktop = memo(() => {
directory={directory as VirtualFolder}
className={styles.Content}
style={{
- "--scale": `${iconScale}rem`
+ "--scale": `${iconScale}rem`,
+ "--direction": iconDirection == 1 ? "row" : "column"
}}
fileClassName={styles["Item"]}
folderClassName={styles["Item"]}
diff --git a/src/config/desktop.config.ts b/src/config/desktop.config.ts
index e1a613d..e56cd52 100644
--- a/src/config/desktop.config.ts
+++ b/src/config/desktop.config.ts
@@ -11,4 +11,5 @@ export const WALLPAPERS = [
];
export const FALLBACK_WALLPAPER = WALLPAPERS[6];
-export const FALLBACK_ICON_SIZE = 1;
\ No newline at end of file
+export const FALLBACK_ICON_SIZE = 1;
+export const FALLBACK_ICON_DIRECTION = 0; // 0: vertical, 1: horizontal
\ No newline at end of file
diff --git a/src/features/virtual-drive/root/defaultData.ts b/src/features/virtual-drive/root/defaultData.ts
index 6c23519..a4505c5 100644
--- a/src/features/virtual-drive/root/defaultData.ts
+++ b/src/features/virtual-drive/root/defaultData.ts
@@ -79,11 +79,18 @@ export function loadDefaultData(virtualRoot: VirtualRoot) {
});
});
- // Create files and folders based on repository tree
+ loadTree(virtualRoot);
+}
+
+// Create files and folders based on repository tree
+function loadTree(virtualRoot: VirtualRoot) {
+ const excludedFiles = [
+ "/public/config/tree.json"
+ ];
+
void fetch("/config/tree.json").then((response) =>
response.json()
).then(({ files, folders }: { files: string[], folders: string[] }) => {
-
folders.forEach((folderPath) => {
const lastSlashIndex = folderPath.lastIndexOf("/");
@@ -100,11 +107,12 @@ export function loadDefaultData(virtualRoot: VirtualRoot) {
});
files.forEach((filePath) => {
+ if (excludedFiles.includes(filePath))
+ return;
+
const lastSlashIndex = filePath.lastIndexOf("/");
const callback = (virtualFile: VirtualFile) => {
- console.log(virtualFile.absolutePath);
-
const virtualPath = virtualFile.absolutePath;
if (virtualPath.startsWith("/public/")) {
virtualFile.setSource(virtualPath.replace(/^\/public\//, "/"));
@@ -126,6 +134,6 @@ export function loadDefaultData(virtualRoot: VirtualRoot) {
parentFolder.createFile(name, extension, callback);
});
}).catch(() => {
- console.warn("Failed to fetch repository tree. Make sure the tree data is valid and up-to-date using 'npm run fetch'.");
+ console.warn("Failed to load repository tree. Make sure the tree data is valid and up-to-date using 'npm run fetch'.");
});
}
\ No newline at end of file
diff --git a/src/features/windows/windowsManager.ts b/src/features/windows/windowsManager.ts
index e948f08..c5e9261 100644
--- a/src/features/windows/windowsManager.ts
+++ b/src/features/windows/windowsManager.ts
@@ -136,6 +136,17 @@ export default class WindowsManager {
return this.windows[windowId].isFocused;
}
+ isAnyFocused() {
+ let anyFocused = false;
+
+ Object.values(this.windows).forEach((window) => {
+ if (window.isFocused)
+ return anyFocused = true;
+ });
+
+ return anyFocused;
+ }
+
/**
* @param minimized - Leave as undefined to toggle the window's minimization state
*/
diff --git a/src/hooks/_utils/keyboard.ts b/src/hooks/_utils/keyboard.ts
index 6e09cc0..411cdbc 100644
--- a/src/hooks/_utils/keyboard.ts
+++ b/src/hooks/_utils/keyboard.ts
@@ -28,6 +28,9 @@ interface UseShortcutsParams {
useCategories?: boolean,
}
+/**
+ * TO DO: rewrite to use a global shortcuts manager instead, to allow certain shortcuts to be prioritized and prevent conflicts
+ */
export function useShortcuts({ options, shortcuts, useCategories = true }: UseShortcutsParams) {
const [activeKeys, setActiveKeys] = useState([]);
@@ -37,10 +40,6 @@ export function useShortcuts({ options, shortcuts, useCategories = true }: UseSh
const keys = [...activeKeys];
- // Prevent alt-tabbing from messing with alt key registration
- if (keys.includes("Alt") && !event.altKey)
- removeFromArray("Alt", keys);
-
const checkGroup = (group: Record, category?: string) => {
for (const [name, shortcut] of Object.entries(group)) {
let active = true;
@@ -60,9 +59,9 @@ export function useShortcuts({ options, shortcuts, useCategories = true }: UseSh
continue;
if (category != null) {
- (options?.[category]?.[name] as Function)?.();
+ (options?.[category]?.[name] as Function)?.(event);
} else {
- (options?.[name] as Function)?.();
+ (options?.[name] as Function)?.(event);
}
}
};
@@ -78,6 +77,18 @@ export function useShortcuts({ options, shortcuts, useCategories = true }: UseSh
setActiveKeys(keys);
}, [activeKeys, options, shortcuts, useCategories]);
+ useEffect(() => {
+ const onBlur = () => {
+ setActiveKeys([]);
+ };
+
+ document.addEventListener("blur", onBlur);
+
+ return () => {
+ document.removeEventListener("blur", onBlur);
+ };
+ }, []);
+
const onKeyDown = (event: KeyboardEvent) => {
const isRepeated = activeKeys.includes(event.key);
checkShortcuts(event, isRepeated);
diff --git a/src/tools/stage.ts b/src/tools/stage.ts
index 7b5d03e..b40e015 100644
--- a/src/tools/stage.ts
+++ b/src/tools/stage.ts
@@ -59,6 +59,23 @@ function generateCname() {
return DOMAIN;
}
+function generateTemplate(html: string) {
+ const baseUrlRegex = /(?<=")https?:\/\/[a-z0-9-]+(\.)([a-zA-Z0-9-]+(\.))*[a-z]{2,3}\/(?=.*")/gi;
+ html = html.replaceAll(baseUrlRegex, BASE_URL);
+
+ const commentsRegex = //g;
+ html = html.replaceAll(commentsRegex, "");
+
+ const emptyLinesRegex = /^\s*$\n/gm;
+ html = html.replaceAll(emptyLinesRegex, "");
+
+ const path = `${BUILD_DIR}/index.html`;
+ fs.writeFileSync(path, html, { flag: "w+" });
+ console.log(`- ${ANSI.fg.cyan}${path}${ANSI.reset}`);
+
+ return html;
+}
+
/**
* To avoid GitHub pages rendering certain pages that are only defined by React router as a 404 page,
* we copy the content of our index file to the 404 page, letting React router properly handle routing on every page
@@ -69,6 +86,9 @@ function generate404Page(template: string) {
console.log(`- ${ANSI.fg.cyan}${path}${ANSI.reset}`);
}
+/**
+ * Add an HTML file for every app page so they can be properly crawled and indexed
+ */
function generateAppPages(template: string) {
for (const [key, value] of Object.entries(APPS)) {
const appId = value;
@@ -120,9 +140,13 @@ try {
});
console.log(`\n${ANSI.fg.yellow}Generating pages...${ANSI.reset}`);
- const template = fs.readFileSync(PATHS.indexHtml, "utf-8");
+
+ const html = fs.readFileSync(PATHS.indexHtml, "utf-8");
+ const template = generateTemplate(html);
+
generate404Page(template);
generateAppPages(template);
+
console.log(`\n${ANSI.fg.green}✓ Staging complete${ANSI.reset}`);
} catch (error) {
console.error(error);